mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 02:58:07 +00:00
Add message editing feature
This commit is contained in:
parent
fa1f8386b4
commit
7972fd7c1f
6 changed files with 343 additions and 28 deletions
|
|
@ -204,6 +204,8 @@
|
||||||
"conversation_dialog_edit_subject" = "Edit conversation subject";
|
"conversation_dialog_edit_subject" = "Edit conversation subject";
|
||||||
"conversation_dialog_set_subject" = "Set conversation subject";
|
"conversation_dialog_set_subject" = "Set conversation subject";
|
||||||
"conversation_dialog_subject_hint" = "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_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_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.";
|
"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_delete_selected_item" = "Delete";
|
||||||
"menu_forward_chat_message" = "Forward";
|
"menu_forward_chat_message" = "Forward";
|
||||||
"menu_invite" = "Invite";
|
"menu_invite" = "Invite";
|
||||||
|
"menu_edit_chat_message" = "Edit";
|
||||||
"menu_reply_to_chat_message" = "Reply";
|
"menu_reply_to_chat_message" = "Reply";
|
||||||
"menu_resend_chat_message" = "Re-send";
|
"menu_resend_chat_message" = "Re-send";
|
||||||
"menu_see_existing_contact" = "See contact";
|
"menu_see_existing_contact" = "See contact";
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,8 @@
|
||||||
"conversation_dialog_edit_subject" = "Renommer la conversation";
|
"conversation_dialog_edit_subject" = "Renommer la conversation";
|
||||||
"conversation_dialog_set_subject" = "Nommer la conversation";
|
"conversation_dialog_set_subject" = "Nommer la conversation";
|
||||||
"conversation_dialog_subject_hint" = "Nom de 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_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_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.";
|
"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_delete_selected_item" = "Supprimer";
|
||||||
"menu_forward_chat_message" = "Transférer";
|
"menu_forward_chat_message" = "Transférer";
|
||||||
"menu_invite" = "Inviter";
|
"menu_invite" = "Inviter";
|
||||||
|
"menu_edit_chat_message" = "Modifier";
|
||||||
"menu_reply_to_chat_message" = "Répondre";
|
"menu_reply_to_chat_message" = "Répondre";
|
||||||
"menu_resend_chat_message" = "Ré-envoyer";
|
"menu_resend_chat_message" = "Ré-envoyer";
|
||||||
"menu_see_existing_contact" = "Voir le contact";
|
"menu_see_existing_contact" = "Voir le contact";
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,14 @@ struct ChatBubbleView: View {
|
||||||
.padding(.top, 1)
|
.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))
|
Text(conversationViewModel.getMessageTime(startDate: eventLogMessage.message.dateReceived))
|
||||||
.foregroundStyle(Color.grayMain2c500)
|
.foregroundStyle(Color.grayMain2c500)
|
||||||
.default_text_style_300(styleSize: 12)
|
.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 {
|
if eventLogMessage.message.isEphemeral && !eventLogMessage.message.isOutgoing {
|
||||||
Image("clock-countdown")
|
Image("clock-countdown")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
|
|
|
||||||
|
|
@ -620,6 +620,43 @@ struct ConversationFragment: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .bottom))
|
.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 {
|
if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading {
|
||||||
|
|
@ -879,43 +916,66 @@ struct ConversationFragment: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
|
if conversationViewModel.messageToEdit == nil {
|
||||||
Button {
|
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
|
||||||
voiceRecordingInProgress = true
|
Button {
|
||||||
} label: {
|
voiceRecordingInProgress = true
|
||||||
Image("microphone")
|
} label: {
|
||||||
.renderingMode(.template)
|
Image("microphone")
|
||||||
.resizable()
|
.renderingMode(.template)
|
||||||
.foregroundStyle(Color.grayMain2c500)
|
.resizable()
|
||||||
.frame(width: 28, height: 28, alignment: .leading)
|
.foregroundStyle(Color.grayMain2c500)
|
||||||
.padding(.all, 6)
|
.frame(width: 28, height: 28, alignment: .leading)
|
||||||
.padding(.top, 4)
|
.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 {
|
} else {
|
||||||
Button {
|
Button {
|
||||||
if conversationViewModel.displayedConversationHistorySize > 1 {
|
|
||||||
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageTextTmp = self.messageText
|
let messageTextTmp = self.messageText
|
||||||
messageText = " "
|
messageText = " "
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||||
messageText = ""
|
messageText = ""
|
||||||
isMessageTextFocused = true
|
isMessageTextFocused = true
|
||||||
|
|
||||||
conversationViewModel.sendMessage(messageText: messageTextTmp)
|
conversationViewModel.sendMessage(messageText: messageTextTmp)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image("paper-plane-tilt")
|
Image("pencil-simple")
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.resizable()
|
.resizable()
|
||||||
.foregroundStyle(Color.orangeMain500)
|
.foregroundStyle(messageText.isEmpty ? Color.gray300 : Color.orangeMain500)
|
||||||
.frame(width: 28, height: 28, alignment: .leading)
|
.frame(width: 28, height: 28, alignment: .leading)
|
||||||
.padding(.all, 6)
|
.padding(.all, 6)
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
.rotationEffect(.degrees(45))
|
|
||||||
}
|
}
|
||||||
.padding(.trailing, 4)
|
.padding(.trailing, 4)
|
||||||
|
.disabled(messageText.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.leading, 15)
|
.padding(.leading, 15)
|
||||||
|
|
@ -1096,6 +1156,43 @@ struct ConversationFragment: View {
|
||||||
|
|
||||||
Divider()
|
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 {
|
Button {
|
||||||
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
||||||
|
|
@ -1211,6 +1308,9 @@ struct ConversationFragment: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
touchFeedback()
|
touchFeedback()
|
||||||
|
if isMessageTextFocused {
|
||||||
|
isMessageTextFocused = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
if conversationViewModel.selectedMessage != nil {
|
if conversationViewModel.selectedMessage != nil {
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ public struct Message: Identifiable, Hashable {
|
||||||
public var status: Status?
|
public var status: Status?
|
||||||
public var createdAt: Date
|
public var createdAt: Date
|
||||||
public var isOutgoing: Bool
|
public var isOutgoing: Bool
|
||||||
|
public var isEditable: Bool
|
||||||
|
public var isEdited: Bool
|
||||||
public var dateReceived: time_t
|
public var dateReceived: time_t
|
||||||
|
|
||||||
public var address: String
|
public var address: String
|
||||||
|
|
@ -94,6 +96,8 @@ public struct Message: Identifiable, Hashable {
|
||||||
status: Status? = nil,
|
status: Status? = nil,
|
||||||
createdAt: Date = Date(),
|
createdAt: Date = Date(),
|
||||||
isOutgoing: Bool,
|
isOutgoing: Bool,
|
||||||
|
isEditable: Bool,
|
||||||
|
isEdited: Bool,
|
||||||
dateReceived: time_t,
|
dateReceived: time_t,
|
||||||
address: String,
|
address: String,
|
||||||
isFirstMessage: Bool = false,
|
isFirstMessage: Bool = false,
|
||||||
|
|
@ -116,6 +120,8 @@ public struct Message: Identifiable, Hashable {
|
||||||
self.status = status
|
self.status = status
|
||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
|
self.isEditable = isEditable
|
||||||
|
self.isEdited = isEdited
|
||||||
self.dateReceived = dateReceived
|
self.dateReceived = dateReceived
|
||||||
self.isFirstMessage = isFirstMessage
|
self.isFirstMessage = isFirstMessage
|
||||||
self.address = address
|
self.address = address
|
||||||
|
|
@ -163,6 +169,8 @@ public struct Message: Identifiable, Hashable {
|
||||||
status: status,
|
status: status,
|
||||||
createdAt: draft.createdAt,
|
createdAt: draft.createdAt,
|
||||||
isOutgoing: draft.isOutgoing,
|
isOutgoing: draft.isOutgoing,
|
||||||
|
isEditable: draft.isEditable,
|
||||||
|
isEdited: draft.isEdited,
|
||||||
dateReceived: draft.dateReceived,
|
dateReceived: draft.dateReceived,
|
||||||
address: draft.address,
|
address: draft.address,
|
||||||
isFirstMessage: draft.isFirstMessage,
|
isFirstMessage: draft.isFirstMessage,
|
||||||
|
|
@ -184,7 +192,7 @@ extension Message {
|
||||||
|
|
||||||
extension Message: Equatable {
|
extension Message: Equatable {
|
||||||
public static func == (lhs: Message, rhs: Message) -> Bool {
|
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 isFirstMessage: Bool
|
||||||
public var text: String
|
public var text: String
|
||||||
public var isOutgoing: Bool
|
public var isOutgoing: Bool
|
||||||
|
public var isEditable: Bool
|
||||||
|
public var isEdited: Bool
|
||||||
public var dateReceived: time_t
|
public var dateReceived: time_t
|
||||||
public var attachmentsNames: String
|
public var attachmentsNames: String
|
||||||
public var attachments: [Attachment]
|
public var attachments: [Attachment]
|
||||||
|
|
@ -221,6 +231,8 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
||||||
isFirstMessage: Bool = false,
|
isFirstMessage: Bool = false,
|
||||||
text: String = "",
|
text: String = "",
|
||||||
isOutgoing: Bool,
|
isOutgoing: Bool,
|
||||||
|
isEditable: Bool,
|
||||||
|
isEdited: Bool,
|
||||||
dateReceived: time_t,
|
dateReceived: time_t,
|
||||||
attachmentsNames: String = "",
|
attachmentsNames: String = "",
|
||||||
attachments: [Attachment] = [],
|
attachments: [Attachment] = [],
|
||||||
|
|
@ -231,6 +243,8 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
||||||
self.isFirstMessage = isFirstMessage
|
self.isFirstMessage = isFirstMessage
|
||||||
self.text = text
|
self.text = text
|
||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
|
self.isEditable = isEditable
|
||||||
|
self.isEdited = isEdited
|
||||||
self.dateReceived = dateReceived
|
self.dateReceived = dateReceived
|
||||||
self.attachmentsNames = attachmentsNames
|
self.attachmentsNames = attachmentsNames
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
|
|
@ -238,20 +252,22 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMessage() -> Message {
|
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 {
|
public extension Message {
|
||||||
|
|
||||||
func toReplyMessage() -> ReplyMessage {
|
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 struct DraftMessage {
|
||||||
public var id: String?
|
public var id: String?
|
||||||
public let isOutgoing: Bool
|
public let isOutgoing: Bool
|
||||||
|
public let isEditable: Bool
|
||||||
|
public let isEdited: Bool
|
||||||
public var dateReceived: time_t
|
public var dateReceived: time_t
|
||||||
public let address: String
|
public let address: String
|
||||||
public let isFirstMessage: Bool
|
public let isFirstMessage: Bool
|
||||||
|
|
@ -265,6 +281,8 @@ public struct DraftMessage {
|
||||||
|
|
||||||
public init(id: String? = nil,
|
public init(id: String? = nil,
|
||||||
isOutgoing: Bool,
|
isOutgoing: Bool,
|
||||||
|
isEditable: Bool,
|
||||||
|
isEdited: Bool,
|
||||||
dateReceived: time_t,
|
dateReceived: time_t,
|
||||||
address: String,
|
address: String,
|
||||||
isFirstMessage: Bool,
|
isFirstMessage: Bool,
|
||||||
|
|
@ -278,6 +296,8 @@ public struct DraftMessage {
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
|
self.isEditable = isEditable
|
||||||
|
self.isEdited = isEdited
|
||||||
self.dateReceived = dateReceived
|
self.dateReceived = dateReceived
|
||||||
self.address = address
|
self.address = address
|
||||||
self.isFirstMessage = isFirstMessage
|
self.isFirstMessage = isFirstMessage
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ class ConversationViewModel: ObservableObject {
|
||||||
@Published var selectedMessageToPlayVoiceRecording: EventLogMessage?
|
@Published var selectedMessageToPlayVoiceRecording: EventLogMessage?
|
||||||
@Published var selectedMessage: EventLogMessage?
|
@Published var selectedMessage: EventLogMessage?
|
||||||
@Published var messageToReply: EventLogMessage?
|
@Published var messageToReply: EventLogMessage?
|
||||||
|
@Published var messageToEdit: EventLogMessage?
|
||||||
|
|
||||||
@Published var sheetCategories: [SheetCategory] = []
|
@Published var sheetCategories: [SheetCategory] = []
|
||||||
|
|
||||||
|
|
@ -171,7 +172,127 @@ class ConversationViewModel: ObservableObject {
|
||||||
self.getEventMessage(eventLog: eventLog)
|
self.getEventMessage(eventLog: eventLog)
|
||||||
}, onEphemeralMessageDeleted: {(_: ChatRoom, eventLog: EventLog) in
|
}, onEphemeralMessageDeleted: {(_: ChatRoom, eventLog: EventLog) in
|
||||||
self.removeMessage(eventLog)
|
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)
|
self.chatRoomDelegateHolder = ChatRoomDelegateHolder(chatroom: chatRoom, delegate: chatRoomDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,6 +665,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
status: nil,
|
status: nil,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
address: "",
|
address: "",
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
|
|
@ -722,6 +845,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
text: contentReplyText,
|
text: contentReplyText,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
attachmentsNames: attachmentNameReplyList,
|
attachmentsNames: attachmentNameReplyList,
|
||||||
attachments: []
|
attachments: []
|
||||||
|
|
@ -735,6 +860,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||||
status: statusTmp,
|
status: statusTmp,
|
||||||
isOutgoing: chatMessage.isOutgoing,
|
isOutgoing: chatMessage.isOutgoing,
|
||||||
|
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||||
|
isEdited: chatMessage.isEdited,
|
||||||
dateReceived: chatMessage.time,
|
dateReceived: chatMessage.time,
|
||||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||||
isFirstMessage: isFirstMessageTmp,
|
isFirstMessage: isFirstMessageTmp,
|
||||||
|
|
@ -788,6 +915,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
status: nil,
|
status: nil,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
address: "",
|
address: "",
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
|
|
@ -965,6 +1094,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
text: contentReplyText,
|
text: contentReplyText,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
attachmentsNames: attachmentNameReplyList,
|
attachmentsNames: attachmentNameReplyList,
|
||||||
attachments: []
|
attachments: []
|
||||||
|
|
@ -978,6 +1109,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||||
status: statusTmp,
|
status: statusTmp,
|
||||||
isOutgoing: chatMessage.isOutgoing,
|
isOutgoing: chatMessage.isOutgoing,
|
||||||
|
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||||
|
isEdited: chatMessage.isEdited,
|
||||||
dateReceived: chatMessage.time,
|
dateReceived: chatMessage.time,
|
||||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||||
isFirstMessage: isFirstMessageTmp,
|
isFirstMessage: isFirstMessageTmp,
|
||||||
|
|
@ -1048,6 +1181,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
status: nil,
|
status: nil,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
address: "",
|
address: "",
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
|
|
@ -1239,6 +1374,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
text: contentReplyText,
|
text: contentReplyText,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
attachmentsNames: attachmentNameReplyList,
|
attachmentsNames: attachmentNameReplyList,
|
||||||
attachments: []
|
attachments: []
|
||||||
|
|
@ -1253,6 +1390,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
appData: chatMessage.appdata ?? "",
|
appData: chatMessage.appdata ?? "",
|
||||||
status: statusTmp,
|
status: statusTmp,
|
||||||
isOutgoing: chatMessage.isOutgoing,
|
isOutgoing: chatMessage.isOutgoing,
|
||||||
|
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||||
|
isEdited: chatMessage.isEdited,
|
||||||
dateReceived: chatMessage.time,
|
dateReceived: chatMessage.time,
|
||||||
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
||||||
isFirstMessage: isFirstMessageTmp,
|
isFirstMessage: isFirstMessageTmp,
|
||||||
|
|
@ -1471,6 +1610,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
text: contentReplyText,
|
text: contentReplyText,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
attachmentsNames: attachmentNameReplyList,
|
attachmentsNames: attachmentNameReplyList,
|
||||||
attachments: []
|
attachments: []
|
||||||
|
|
@ -1485,6 +1626,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
appData: chatMessage.appdata ?? "",
|
appData: chatMessage.appdata ?? "",
|
||||||
status: statusTmp,
|
status: statusTmp,
|
||||||
isOutgoing: chatMessage.isOutgoing,
|
isOutgoing: chatMessage.isOutgoing,
|
||||||
|
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||||
|
isEdited: chatMessage.isEdited,
|
||||||
dateReceived: chatMessage.time,
|
dateReceived: chatMessage.time,
|
||||||
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
|
||||||
isFirstMessage: isFirstMessageTmp,
|
isFirstMessage: isFirstMessageTmp,
|
||||||
|
|
@ -1526,6 +1669,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
status: nil,
|
status: nil,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
address: "",
|
address: "",
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
|
|
@ -1553,6 +1698,9 @@ class ConversationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func replyToMessage(index: Int, isMessageTextFocused: Binding<Bool>) {
|
func replyToMessage(index: Int, isMessageTextFocused: Binding<Bool>) {
|
||||||
|
if self.messageToEdit != nil {
|
||||||
|
self.messageToEdit = nil
|
||||||
|
}
|
||||||
coreContext.doOnCoreQueue { _ in
|
coreContext.doOnCoreQueue { _ in
|
||||||
let messageToReplyTmp = self.conversationMessagesSection[0].rows[index]
|
let messageToReplyTmp = self.conversationMessagesSection[0].rows[index]
|
||||||
DispatchQueue.main.async {
|
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) {
|
func resendMessage(chatMessage: EventLogMessage) {
|
||||||
coreContext.doOnCoreQueue { _ in
|
coreContext.doOnCoreQueue { _ in
|
||||||
if let message = chatMessage.eventModel.eventLog.chatMessage {
|
if let message = chatMessage.eventModel.eventLog.chatMessage {
|
||||||
|
|
@ -1619,6 +1782,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
status: nil,
|
status: nil,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
address: "",
|
address: "",
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
|
|
@ -1796,6 +1961,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
isFirstMessage: false,
|
isFirstMessage: false,
|
||||||
text: contentReplyText,
|
text: contentReplyText,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
|
isEditable: false,
|
||||||
|
isEdited: false,
|
||||||
dateReceived: 0,
|
dateReceived: 0,
|
||||||
attachmentsNames: attachmentNameReplyList,
|
attachmentsNames: attachmentNameReplyList,
|
||||||
attachments: []
|
attachments: []
|
||||||
|
|
@ -1809,6 +1976,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
|
||||||
status: statusTmp,
|
status: statusTmp,
|
||||||
isOutgoing: chatMessage.isOutgoing,
|
isOutgoing: chatMessage.isOutgoing,
|
||||||
|
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
|
||||||
|
isEdited: chatMessage.isEdited,
|
||||||
dateReceived: chatMessage.time,
|
dateReceived: chatMessage.time,
|
||||||
address: addressCleaned?.asStringUriOnly() ?? "",
|
address: addressCleaned?.asStringUriOnly() ?? "",
|
||||||
isFirstMessage: isFirstMessageTmp,
|
isFirstMessage: isFirstMessageTmp,
|
||||||
|
|
@ -1873,6 +2042,8 @@ class ConversationViewModel: ObservableObject {
|
||||||
if chatMessageToReply != nil {
|
if chatMessageToReply != nil {
|
||||||
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplyMessage(message: chatMessageToReply!)
|
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 {
|
} else {
|
||||||
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createEmptyMessage()
|
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createEmptyMessage()
|
||||||
}
|
}
|
||||||
|
|
@ -1948,12 +2119,14 @@ class ConversationViewModel: ObservableObject {
|
||||||
if message != nil && !message!.contents.isEmpty {
|
if message != nil && !message!.contents.isEmpty {
|
||||||
Log.info("[ConversationViewModel] Sending message")
|
Log.info("[ConversationViewModel] Sending message")
|
||||||
message!.send()
|
message!.send()
|
||||||
|
self.sharedMainViewModel.displayedConversation!.chatRoom.stopComposing()
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("[ConversationViewModel] Message sent, re-setting defaults")
|
Log.info("[ConversationViewModel] Message sent, re-setting defaults")
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.messageToReply = nil
|
self.messageToReply = nil
|
||||||
|
self.messageToEdit = nil
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.mediasToSend.removeAll()
|
self.mediasToSend.removeAll()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue