diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 6c4eeda84..b0c478b0d 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; }; 6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */; }; @@ -364,7 +364,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */, + 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1238,7 +1238,7 @@ INFOPLIST_FILE = msgNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1277,7 +1277,7 @@ INFOPLIST_FILE = msgNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1422,7 +1422,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 36; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; @@ -1478,7 +1478,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 36; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; DEVELOPMENT_TEAM = Z2V957B3D6; diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index d7fed15c1..24f5b4e58 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -24,7 +24,7 @@ struct ChatBubbleView: View { @ObservedObject var conversationViewModel: ConversationViewModel - let message: Message + let eventLogMessage: EventLogMessage let geometryProxy: GeometryProxy @@ -35,50 +35,50 @@ struct ChatBubbleView: View { var body: some View { HStack { VStack { - if !message.text.isEmpty || !message.attachments.isEmpty { + if !eventLogMessage.message.text.isEmpty || !eventLogMessage.message.attachments.isEmpty { HStack(alignment: .top, content: { - if message.isOutgoing { + if eventLogMessage.message.isOutgoing { Spacer() } - if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !message.isOutgoing && message.isFirstMessage { + if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !eventLogMessage.message.isOutgoing && eventLogMessage.message.isFirstMessage { VStack { Avatar( - contactAvatarModel: conversationViewModel.participantConversationModel.first(where: {$0.address == message.address}) ?? + contactAvatarModel: conversationViewModel.participantConversationModel.first(where: {$0.address == eventLogMessage.message.address}) ?? ContactAvatarModel(friend: nil, name: "??", address: "", withPresence: false), avatarSize: 35 ) .padding(.top, 30) } - } else if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !message.isOutgoing { + } else if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !eventLogMessage.message.isOutgoing { VStack { } .padding(.leading, 43) } VStack(alignment: .leading, spacing: 0) { - if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !message.isOutgoing && message.isFirstMessage { - Text(conversationViewModel.participantConversationModel.first(where: {$0.address == message.address})?.name ?? "") + if conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup && !eventLogMessage.message.isOutgoing && eventLogMessage.message.isFirstMessage { + Text(conversationViewModel.participantConversationModel.first(where: {$0.address == eventLogMessage.message.address})?.name ?? "") .default_text_style(styleSize: 12) .padding(.top, 10) .padding(.bottom, 2) } - if message.replyMessage != nil { + if eventLogMessage.message.replyMessage != nil { HStack { - if message.isOutgoing { + if eventLogMessage.message.isOutgoing { Spacer() } - VStack(alignment: message.isOutgoing ? .trailing : .leading) { - VStack(alignment: message.isOutgoing ? .trailing : .leading) { - if !message.replyMessage!.text.isEmpty { - Text(message.replyMessage!.text) + VStack(alignment: eventLogMessage.message.isOutgoing ? .trailing : .leading) { + VStack(alignment: eventLogMessage.message.isOutgoing ? .trailing : .leading) { + if !eventLogMessage.message.replyMessage!.text.isEmpty { + Text(eventLogMessage.message.replyMessage!.text) .foregroundStyle(Color.grayMain2c700) .default_text_style(styleSize: 16) .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/) - } else if !message.replyMessage!.attachmentsNames.isEmpty { - Text(message.replyMessage!.attachmentsNames) + } else if !eventLogMessage.message.replyMessage!.attachmentsNames.isEmpty { + Text(eventLogMessage.message.replyMessage!.attachmentsNames) .foregroundStyle(Color.grayMain2c700) .default_text_style(styleSize: 16) .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/) @@ -90,14 +90,14 @@ struct ChatBubbleView: View { .clipShape(RoundedRectangle(cornerRadius: 1)) .roundedCorner( 16, - corners: message.isOutgoing ? [.topLeft, .topRight, .bottomLeft] : [.topLeft, .topRight, .bottomRight] + corners: eventLogMessage.message.isOutgoing ? [.topLeft, .topRight, .bottomLeft] : [.topLeft, .topRight, .bottomRight] ) } .onTapGesture { - conversationViewModel.scrollToMessage(message: message) + conversationViewModel.scrollToMessage(message: eventLogMessage.message) } - if !message.isOutgoing { + if !eventLogMessage.message.isOutgoing { Spacer() } } @@ -107,37 +107,37 @@ struct ChatBubbleView: View { ZStack { HStack { - if message.isOutgoing { + if eventLogMessage.message.isOutgoing { Spacer() } - VStack(alignment: message.isOutgoing ? .trailing : .leading) { - VStack(alignment: message.isOutgoing ? .trailing : .leading) { - if !message.attachments.isEmpty { + VStack(alignment: eventLogMessage.message.isOutgoing ? .trailing : .leading) { + VStack(alignment: eventLogMessage.message.isOutgoing ? .trailing : .leading) { + if !eventLogMessage.message.attachments.isEmpty { messageAttachments() } - if !message.text.isEmpty { - Text(message.text) + if !eventLogMessage.message.text.isEmpty { + Text(eventLogMessage.message.text) .foregroundStyle(Color.grayMain2c700) .default_text_style(styleSize: 16) } HStack(alignment: .center) { - Text(conversationViewModel.getMessageTime(startDate: message.dateReceived)) + Text(conversationViewModel.getMessageTime(startDate: eventLogMessage.message.dateReceived)) .foregroundStyle(Color.grayMain2c500) .default_text_style_300(styleSize: 14) .padding(.top, 1) - if (conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup) || message.isOutgoing { - if message.status == .sending { + if (conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup) || eventLogMessage.message.isOutgoing { + if eventLogMessage.message.status == .sending { ProgressView() .controlSize(.mini) .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) .frame(width: 10, height: 10) .padding(.top, 1) - } else if message.status != nil { - Image(conversationViewModel.getImageIMDN(status: message.status!)) + } else if eventLogMessage.message.status != nil { + Image(conversationViewModel.getImageIMDN(status: eventLogMessage.message.status!)) .renderingMode(.template) .resizable() .foregroundStyle(Color.orangeMain500) @@ -149,49 +149,49 @@ struct ChatBubbleView: View { .padding(.top, -4) } .padding(.all, 15) - .background(message.isOutgoing ? Color.orangeMain100 : Color.grayMain2c100) + .background(eventLogMessage.message.isOutgoing ? Color.orangeMain100 : Color.grayMain2c100) .clipShape(RoundedRectangle(cornerRadius: 3)) .roundedCorner( 16, - corners: message.isOutgoing && message.isFirstMessage ? [.topLeft, .topRight, .bottomLeft] : - (!message.isOutgoing && message.isFirstMessage ? [.topRight, .bottomRight, .bottomLeft] : [.allCorners])) + corners: eventLogMessage.message.isOutgoing && eventLogMessage.message.isFirstMessage ? [.topLeft, .topRight, .bottomLeft] : + (!eventLogMessage.message.isOutgoing && eventLogMessage.message.isFirstMessage ? [.topRight, .bottomRight, .bottomLeft] : [.allCorners])) - if !message.reactions.isEmpty { + if !eventLogMessage.message.reactions.isEmpty { HStack { - ForEach(0.. some View { - if message.attachments.count == 1 { - if message.attachments.first!.type == .image || message.attachments.first!.type == .gif || message.attachments.first!.type == .video { - let result = imageDimensions(url: message.attachments.first!.thumbnail.absoluteString) + if eventLogMessage.message.attachments.count == 1 { + if eventLogMessage.message.attachments.first!.type == .image || eventLogMessage.message.attachments.first!.type == .gif || eventLogMessage.message.attachments.first!.type == .video { + let result = imageDimensions(url: eventLogMessage.message.attachments.first!.thumbnail.absoluteString) ZStack { Rectangle() .fill(Color(.white)) @@ -268,9 +268,9 @@ struct ChatBubbleView: View { ) } - if message.attachments.first!.type == .image || message.attachments.first!.type == .video { + if eventLogMessage.message.attachments.first!.type == .image || eventLogMessage.message.attachments.first!.type == .video { if #available(iOS 16.0, *) { - AsyncImage(url: message.attachments.first!.thumbnail) { phase in + AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in switch phase { case .empty: ProgressView() @@ -281,7 +281,7 @@ struct ChatBubbleView: View { .interpolation(.medium) .aspectRatio(contentMode: .fill) - if message.attachments.first!.type == .video { + if eventLogMessage.message.attachments.first!.type == .video { Image("play-fill") .renderingMode(.template) .resizable() @@ -298,7 +298,7 @@ struct ChatBubbleView: View { .layoutPriority(-1) .clipShape(RoundedRectangle(cornerRadius: 4)) } else { - AsyncImage(url: message.attachments.first!.thumbnail) { phase in + AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in switch phase { case .empty: ProgressView() @@ -309,7 +309,7 @@ struct ChatBubbleView: View { .interpolation(.medium) .aspectRatio(contentMode: .fill) - if message.attachments.first!.type == .video { + if eventLogMessage.message.attachments.first!.type == .video { Image("play-fill") .renderingMode(.template) .resizable() @@ -327,13 +327,13 @@ struct ChatBubbleView: View { .clipShape(RoundedRectangle(cornerRadius: 4)) .id(UUID()) } - } else if message.attachments.first!.type == .gif { + } else if eventLogMessage.message.attachments.first!.type == .gif { if #available(iOS 16.0, *) { - GifImageView(message.attachments.first!.thumbnail) + GifImageView(eventLogMessage.message.attachments.first!.thumbnail) .layoutPriority(-1) .clipShape(RoundedRectangle(cornerRadius: 4)) } else { - GifImageView(message.attachments.first!.thumbnail) + GifImageView(eventLogMessage.message.attachments.first!.thumbnail) .id(UUID()) .layoutPriority(-1) .clipShape(RoundedRectangle(cornerRadius: 4)) @@ -343,12 +343,12 @@ struct ChatBubbleView: View { .clipShape(RoundedRectangle(cornerRadius: 4)) .clipped() } - } else if message.attachments.count > 1 { + } else if eventLogMessage.message.attachments.count > 1 { let isGroup = conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup LazyVGrid(columns: [ GridItem(.adaptive(minimum: 120), spacing: 1) ], spacing: 3) { - ForEach(message.attachments) { attachment in + ForEach(eventLogMessage.message.attachments) { attachment in ZStack { Rectangle() .fill(Color(.white)) @@ -402,9 +402,9 @@ struct ChatBubbleView: View { } } .frame( - width: geometryProxy.size.width > 0 && CGFloat(122 * message.attachments.count) > geometryProxy.size.width - 110 - (isGroup ? 40 : 0) + width: geometryProxy.size.width > 0 && CGFloat(122 * eventLogMessage.message.attachments.count) > geometryProxy.size.width - 110 - (isGroup ? 40 : 0) ? 122 * floor(CGFloat(geometryProxy.size.width - 110 - (isGroup ? 40 : 0)) / 122) - : CGFloat(122 * message.attachments.count) + : CGFloat(122 * eventLogMessage.message.attachments.count) ) } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 1be552038..9cd784708 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -235,8 +235,8 @@ struct ConversationFragment: View { if conversationViewModel.conversationMessagesSection.first != nil { let counter = conversationViewModel.conversationMessagesSection.first!.rows.count ForEach(0.. 50 ? 50 : iconSize) } .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.ownReaction == "👍" ? Color.gray200 : .white) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white) .cornerRadius(10) Button { @@ -608,7 +614,7 @@ struct ConversationFragment: View { .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.ownReaction == "❤️" ? Color.gray200 : .white) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white) .cornerRadius(10) Button { @@ -618,7 +624,7 @@ struct ConversationFragment: View { .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.ownReaction == "😂" ? Color.gray200 : .white) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white) .cornerRadius(10) Button { @@ -628,7 +634,7 @@ struct ConversationFragment: View { .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.ownReaction == "😮" ? Color.gray200 : .white) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white) .cornerRadius(10) Button { @@ -638,7 +644,7 @@ struct ConversationFragment: View { .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.ownReaction == "😢" ? Color.gray200 : .white) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white) .cornerRadius(10) Button { @@ -656,7 +662,7 @@ struct ConversationFragment: View { .background(.white) .cornerRadius(20) - if !conversationViewModel.selectedMessage!.isOutgoing { + if !conversationViewModel.selectedMessage!.message.isOutgoing { Spacer() } } @@ -665,19 +671,19 @@ struct ConversationFragment: View { .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) .shadow(color: .black.opacity(0.1), radius: 10) - ChatBubbleView(conversationViewModel: conversationViewModel, message: conversationViewModel.selectedMessage!, geometryProxy: geometry) + ChatBubbleView(conversationViewModel: conversationViewModel, eventLogMessage: conversationViewModel.selectedMessage!, geometryProxy: geometry) .padding(.horizontal, 10) .padding(.vertical, 1) .shadow(color: .black.opacity(0.1), radius: 10) HStack { - if conversationViewModel.selectedMessage!.isOutgoing { + if conversationViewModel.selectedMessage!.message.isOutgoing { Spacer() } VStack { Button { - let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == conversationViewModel.selectedMessage!.id}) + let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id}) conversationViewModel.selectedMessage = nil conversationViewModel.replyToMessage(index: indexMessage ?? 0) } label: { @@ -695,10 +701,10 @@ struct ConversationFragment: View { Divider() - if !conversationViewModel.selectedMessage!.text.isEmpty { + if !conversationViewModel.selectedMessage!.message.text.isEmpty { Button { UIPasteboard.general.setValue( - conversationViewModel.selectedMessage!.text, + conversationViewModel.selectedMessage!.message.text, forPasteboardType: UTType.plainText.identifier ) @@ -760,7 +766,7 @@ struct ConversationFragment: View { .background(.white) .cornerRadius(20) - if !conversationViewModel.selectedMessage!.isOutgoing { + if !conversationViewModel.selectedMessage!.message.isOutgoing { Spacer() } } diff --git a/Linphone/UI/Main/Conversations/Fragments/UIList.swift b/Linphone/UI/Main/Conversations/Fragments/UIList.swift index db7870bbd..ee8d2f376 100644 --- a/Linphone/UI/Main/Conversations/Fragments/UIList.swift +++ b/Linphone/UI/Main/Conversations/Fragments/UIList.swift @@ -19,6 +19,7 @@ // swiftlint:disable large_tuple import SwiftUI +import linphonesw public extension Notification.Name { static let onScrollToBottom = Notification.Name("onScrollToBottom") @@ -64,7 +65,7 @@ struct UIList: UIViewRepresentable { && conversationViewModel.displayedConversation != nil && context.coordinator.sections.first!.chatRoomID == conversationViewModel.displayedConversation!.id && context.coordinator.sections.first!.rows.count == conversationViewModel.conversationMessagesSection.first!.rows.count { - tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .bottom, animated: true) + tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) } else { NotificationCenter.default.removeObserver(self, name: .onScrollToBottom, object: nil) } @@ -209,24 +210,26 @@ struct UIList: UIViewRepresentable { var oldRows = appliedDeletes[oldIndex].rows var newRows = appliedDeletesSwapsAndEdits[newIndex].rows - let oldRowIDs = Set(oldRows.map { $0.id }) - let newRowIDs = Set(newRows.map { $0.id }) + let oldRowIDs = Set(oldRows.map { $0.message.id }) + let newRowIDs = Set(newRows.map { $0.message.id }) let rowIDsToDelete = oldRowIDs.subtracting(newRowIDs) let rowIDsToInsert = newRowIDs.subtracting(oldRowIDs) + for rowId in rowIDsToDelete { - if let index = oldRows.firstIndex(where: { $0.id == rowId }) { + if let index = oldRows.firstIndex(where: { $0.message.id == rowId }) { oldRows.remove(at: index) deleteOperations.append(.delete(oldIndex, index)) } } + for rowId in rowIDsToInsert { - if let index = newRows.firstIndex(where: { $0.id == rowId }) { + if let index = newRows.firstIndex(where: { $0.message.id == rowId }) { insertOperations.append(.insert(newIndex, index)) } } for rowId in rowIDsToInsert { - if let index = newRows.firstIndex(where: { $0.id == rowId }) { + if let index = newRows.firstIndex(where: { $0.message.id == rowId }) { newRows.remove(at: index) } } @@ -234,8 +237,8 @@ struct UIList: UIViewRepresentable { for row in 0..= scrollView.contentSize.height - scrollView.frame.height - 200 { self.conversationViewModel.getOldMessages() } - isScrolledToTop = scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height - 200 + + DispatchQueue.main.async { + self.isScrolledToTop = scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height - 200 + } } func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { @@ -390,7 +396,7 @@ struct MessagesSection: Equatable { let date: Date let chatRoomID: String - var rows: [Message] + var rows: [EventLogMessage] static var formatter = { let formatter = DateFormatter() @@ -398,7 +404,7 @@ struct MessagesSection: Equatable { return formatter }() - init(date: Date, chatRoomID: String, rows: [Message]) { + init(date: Date, chatRoomID: String, rows: [EventLogMessage]) { self.date = date self.chatRoomID = chatRoomID self.rows = rows @@ -414,6 +420,28 @@ struct MessagesSection: Equatable { } +struct EventLogMessage: Equatable { + + let eventLog: EventLog + var message: Message + + static var formatter = { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE, MMMM d" + return formatter + }() + + init(eventLog: EventLog, message: Message) { + self.eventLog = eventLog + self.message = message + } + + static func == (lhs: EventLogMessage, rhs: EventLogMessage) -> Bool { + lhs.message == rhs.message + } + +} + final class PaginationState: ObservableObject { var onEvent: ChatPaginationClosure? var offset: Int diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index a351c04b9..051074283 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -62,6 +62,7 @@ public struct Message: Identifiable, Hashable { } public var id: String + public var appData: String public var status: Status? public var createdAt: Date public var isOutgoing: Bool @@ -79,6 +80,7 @@ public struct Message: Identifiable, Hashable { public init( id: String, + appData: String = "", status: Status? = nil, createdAt: Date = Date(), isOutgoing: Bool, @@ -94,6 +96,7 @@ public struct Message: Identifiable, Hashable { reactions: [String] = [] ) { self.id = id + self.appData = appData self.status = status self.createdAt = createdAt self.isOutgoing = isOutgoing diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 6d267ac39..72131d663 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -45,8 +45,8 @@ class ConversationViewModel: ObservableObject { var oldMessageReceived = false - @Published var selectedMessage: Message? - @Published var messageToReply: Message? + @Published var selectedMessage: EventLogMessage? + @Published var messageToReply: EventLogMessage? init() {} @@ -79,22 +79,24 @@ class ConversationViewModel: ObservableObject { statusTmp = .received case .Displayed: statusTmp = .read + case .NotDelivered: + statusTmp = .error default: statusTmp = .sending } - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + var indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventLog.chatMessage?.messageId == message.messageId}) DispatchQueue.main.async { if indexMessage != nil { self.objectWillChange.send() - self.conversationMessagesSection[0].rows[indexMessage!].status = statusTmp + self.conversationMessagesSection[0].rows[indexMessage!].message.status = statusTmp } } }) self.chatMessageSuscriptions.insert(message.publisher?.onNewMessageReaction?.postOnCoreQueue {(cbValue: (message: ChatMessage, reaction: ChatMessageReaction)) in - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventLog.chatMessage?.messageId == message.messageId}) var reactionsTmp: [String] = [] cbValue.message.reactions.forEach({ chatMessageReaction in reactionsTmp.append(chatMessageReaction.body) @@ -103,13 +105,13 @@ class ConversationViewModel: ObservableObject { DispatchQueue.main.async { if indexMessage != nil { self.objectWillChange.send() - self.conversationMessagesSection[0].rows[indexMessage!].reactions = reactionsTmp + self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp } } }) self.chatMessageSuscriptions.insert(message.publisher?.onReactionRemoved?.postOnCoreQueue {(cbValue: (message: ChatMessage, address: Address)) in - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventLog.chatMessage?.messageId == message.messageId}) var reactionsTmp: [String] = [] cbValue.message.reactions.forEach({ chatMessageReaction in reactionsTmp.append(chatMessageReaction.body) @@ -118,7 +120,7 @@ class ConversationViewModel: ObservableObject { DispatchQueue.main.async { if indexMessage != nil { self.objectWillChange.send() - self.conversationMessagesSection[0].rows[indexMessage!].reactions = reactionsTmp + self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp } } }) @@ -156,13 +158,15 @@ class ConversationViewModel: ObservableObject { func markAsRead() { coreContext.doOnCoreQueue { _ in - let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount - - if unreadMessagesCount > 0 { - self.displayedConversation!.chatRoom.markAsRead() + if self.displayedConversation != nil { + let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount - DispatchQueue.main.async { - self.displayedConversationUnreadMessagesCount = 0 + if unreadMessagesCount > 0 { + self.displayedConversation!.chatRoom.markAsRead() + + DispatchQueue.main.async { + self.displayedConversationUnreadMessagesCount = 0 + } } } } @@ -206,7 +210,7 @@ class ConversationViewModel: ObservableObject { if self.displayedConversation != nil { let historyEvents = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: 0, end: 30) - var conversationMessage: [Message] = [] + var conversationMessage: [EventLogMessage] = [] historyEvents.enumerated().forEach { index, eventLog in var attachmentNameList: String = "" @@ -342,36 +346,42 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil { conversationMessage.append( - Message( - id: eventLog.chatMessage?.messageId ?? UUID().uuidString, - status: statusTmp, - isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - dateReceived: eventLog.chatMessage?.time ?? 0, - address: addressCleaned?.asStringUriOnly() ?? "", - isFirstMessage: isFirstMessageTmp, - text: contentText, - attachmentsNames: attachmentNameList, - attachments: attachmentList, - replyMessage: replyMessageTmp, - ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", - reactions: reactionsTmp + EventLogMessage( + eventLog: eventLog, + message: Message( + id: !eventLog.chatMessage!.messageId.isEmpty ? eventLog.chatMessage!.messageId : UUID().uuidString, + status: statusTmp, + isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, + dateReceived: eventLog.chatMessage?.time ?? 0, + address: addressCleaned?.asStringUriOnly() ?? "", + isFirstMessage: isFirstMessageTmp, + text: contentText, + attachmentsNames: attachmentNameList, + attachments: attachmentList, + replyMessage: replyMessageTmp, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp + ) ) ) self.addChatMessageDelegate(message: eventLog.chatMessage!) } else { conversationMessage.insert( - Message( - id: UUID().uuidString, - status: nil, - isOutgoing: false, - dateReceived: 0, - address: "", - isFirstMessage: false, - text: "", - attachments: [], - ownReaction: "", - reactions: [] + EventLogMessage( + eventLog: eventLog, + message: Message( + id: UUID().uuidString, + status: nil, + isOutgoing: false, + dateReceived: 0, + address: "", + isFirstMessage: false, + text: "", + attachments: [], + ownReaction: "", + reactions: [] + ) ), at: 0 ) } @@ -391,7 +401,7 @@ class ConversationViewModel: ObservableObject { if self.displayedConversation != nil && self.displayedConversationHistorySize > self.conversationMessagesSection[0].rows.count && !self.oldMessageReceived { self.oldMessageReceived = true let historyEvents = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: self.conversationMessagesSection[0].rows.count, end: self.conversationMessagesSection[0].rows.count + 30) - var conversationMessagesTmp: [Message] = [] + var conversationMessagesTmp: [EventLogMessage] = [] historyEvents.enumerated().reversed().forEach { index, eventLog in var attachmentNameList: String = "" @@ -479,6 +489,8 @@ class ConversationViewModel: ObservableObject { statusTmp = .received case .Displayed: statusTmp = .read + case .NotDelivered: + statusTmp = .error default: statusTmp = .sending } @@ -525,36 +537,42 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil { conversationMessagesTmp.insert( - Message( - id: eventLog.chatMessage?.messageId ?? UUID().uuidString, - status: statusTmp, - isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - dateReceived: eventLog.chatMessage?.time ?? 0, - address: addressCleaned?.asStringUriOnly() ?? "", - isFirstMessage: isFirstMessageTmp, - text: contentText, - attachmentsNames: attachmentNameList, - attachments: attachmentList, - replyMessage: replyMessageTmp, - ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", - reactions: reactionsTmp + EventLogMessage( + eventLog: eventLog, + message: Message( + id: !eventLog.chatMessage!.messageId.isEmpty ? eventLog.chatMessage!.messageId : UUID().uuidString, + status: statusTmp, + isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, + dateReceived: eventLog.chatMessage?.time ?? 0, + address: addressCleaned?.asStringUriOnly() ?? "", + isFirstMessage: isFirstMessageTmp, + text: contentText, + attachmentsNames: attachmentNameList, + attachments: attachmentList, + replyMessage: replyMessageTmp, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp + ) ), at: 0 ) self.addChatMessageDelegate(message: eventLog.chatMessage!) } else { conversationMessagesTmp.insert( - Message( - id: UUID().uuidString, - status: nil, - isOutgoing: false, - dateReceived: 0, - address: "", - isFirstMessage: false, - text: "", - attachments: [], - ownReaction: "", - reactions: [] + EventLogMessage( + eventLog: eventLog, + message: Message( + id: UUID().uuidString, + status: nil, + isOutgoing: false, + dateReceived: 0, + address: "", + isFirstMessage: false, + text: "", + attachments: [], + ownReaction: "", + reactions: [] + ) ), at: 0 ) } @@ -562,8 +580,8 @@ class ConversationViewModel: ObservableObject { if !conversationMessagesTmp.isEmpty { DispatchQueue.main.async { - if self.conversationMessagesSection[0].rows.last?.address == conversationMessagesTmp.last?.address { - self.conversationMessagesSection[0].rows[self.conversationMessagesSection[0].rows.count - 1].isFirstMessage = false + if self.conversationMessagesSection[0].rows.last?.message.address == conversationMessagesTmp.last?.message.address { + self.conversationMessagesSection[0].rows[self.conversationMessagesSection[0].rows.count - 1].message.isFirstMessage = false } self.conversationMessagesSection[0].rows.append(contentsOf: conversationMessagesTmp.reversed()) self.oldMessageReceived = false @@ -650,7 +668,7 @@ class ConversationViewModel: ObservableObject { : ( self.conversationMessagesSection.isEmpty || self.conversationMessagesSection[0].rows.isEmpty ? true - : self.conversationMessagesSection[0].rows[0].address != addressCleaned?.asStringUriOnly() + : self.conversationMessagesSection[0].rows[0].message.address != addressCleaned?.asStringUriOnly() ) let isFirstMessageOutgoingTmp = index <= eventLogs.count - 2 @@ -658,7 +676,7 @@ class ConversationViewModel: ObservableObject { : ( self.conversationMessagesSection.isEmpty || self.conversationMessagesSection[0].rows.isEmpty ? true - : !self.conversationMessagesSection[0].rows[0].isOutgoing || self.conversationMessagesSection[0].rows[0].address == addressCleaned?.asStringUriOnly() + : !self.conversationMessagesSection[0].rows[0].message.isOutgoing || self.conversationMessagesSection[0].rows[0].message.address == addressCleaned?.asStringUriOnly() ) let isFirstMessageTmp = (eventLog.chatMessage?.isOutgoing ?? false) ? isFirstMessageOutgoingTmp : isFirstMessageIncomingTmp @@ -675,6 +693,8 @@ class ConversationViewModel: ObservableObject { statusTmp = .received case .Displayed: statusTmp = .read + case .NotDelivered: + statusTmp = .error default: statusTmp = .sending } @@ -720,19 +740,23 @@ class ConversationViewModel: ObservableObject { } if eventLog.chatMessage != nil { - let message = Message( - id: eventLog.chatMessage?.messageId ?? UUID().uuidString, - status: statusTmp, - isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - dateReceived: eventLog.chatMessage?.time ?? 0, - address: addressCleaned?.asStringUriOnly() ?? "", - isFirstMessage: isFirstMessageTmp, - text: contentText, - attachmentsNames: attachmentNameList, - attachments: attachmentList, - replyMessage: replyMessageTmp, - ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", - reactions: reactionsTmp + let message = EventLogMessage( + eventLog: eventLog, + message: Message( + id: !eventLog.chatMessage!.messageId.isEmpty ? eventLog.chatMessage!.messageId : UUID().uuidString, + appData: eventLog.chatMessage!.appdata ?? "", + status: statusTmp, + isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, + dateReceived: eventLog.chatMessage?.time ?? 0, + address: addressCleaned?.asStringUriOnly() ?? "", + isFirstMessage: isFirstMessageTmp, + text: contentText, + attachmentsNames: attachmentNameList, + attachments: attachmentList, + replyMessage: replyMessageTmp, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp + ) ) self.addChatMessageDelegate(message: eventLog.chatMessage!) @@ -740,9 +764,9 @@ class ConversationViewModel: ObservableObject { DispatchQueue.main.async { if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty - && self.conversationMessagesSection[0].rows[0].isOutgoing - && (self.conversationMessagesSection[0].rows[0].address == message.address) { - self.conversationMessagesSection[0].rows[0].isFirstMessage = false + && self.conversationMessagesSection[0].rows[0].message.isOutgoing + && (self.conversationMessagesSection[0].rows[0].message.address == message.message.address) { + self.conversationMessagesSection[0].rows[0].message.isFirstMessage = false } if self.conversationMessagesSection.isEmpty && self.displayedConversation != nil { @@ -751,22 +775,25 @@ class ConversationViewModel: ObservableObject { self.conversationMessagesSection[0].rows.insert(message, at: 0) } - if !message.isOutgoing { + if !message.message.isOutgoing { self.displayedConversationUnreadMessagesCount = unreadMessagesCount } } } else { - let message = Message( - id: UUID().uuidString, - status: nil, - isOutgoing: false, - dateReceived: 0, - address: "", - isFirstMessage: false, - text: "", - attachments: [], - ownReaction: "", - reactions: [] + let message = EventLogMessage( + eventLog: eventLog, + message: Message( + id: UUID().uuidString, + status: nil, + isOutgoing: false, + dateReceived: 0, + address: "", + isFirstMessage: false, + text: "", + attachments: [], + ownReaction: "", + reactions: [] + ) ) DispatchQueue.main.async { @@ -795,10 +822,11 @@ class ConversationViewModel: ObservableObject { } } + // swiftlint:disable cyclomatic_complexity func scrollToMessage(message: Message) { coreContext.doOnCoreQueue { _ in if message.replyMessage != nil { - if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.replyMessage!.id}) { + if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventLog.chatMessage?.messageId == message.replyMessage!.id}) { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onScrollToIndex"), object: nil, userInfo: ["index": indexMessage, "animated": true]) } else { if self.conversationMessagesSection[0].rows.last != nil { @@ -825,7 +853,7 @@ class ConversationViewModel: ObservableObject { historyEvents.insert(contentsOf: historyEventsAfter, at: 0) - var conversationMessagesTmp: [Message] = [] + var conversationMessagesTmp: [EventLogMessage] = [] historyEvents.enumerated().reversed().forEach { index, eventLog in var attachmentNameList: String = "" @@ -913,6 +941,8 @@ class ConversationViewModel: ObservableObject { statusTmp = .received case .Displayed: statusTmp = .read + case .NotDelivered: + statusTmp = .error default: statusTmp = .sending } @@ -959,36 +989,42 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil { conversationMessagesTmp.insert( - Message( - id: eventLog.chatMessage?.messageId ?? UUID().uuidString, - status: statusTmp, - isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - dateReceived: eventLog.chatMessage?.time ?? 0, - address: addressCleaned?.asStringUriOnly() ?? "", - isFirstMessage: isFirstMessageTmp, - text: contentText, - attachmentsNames: attachmentNameList, - attachments: attachmentList, - replyMessage: replyMessageTmp, - ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", - reactions: reactionsTmp + EventLogMessage( + eventLog: eventLog, + message: Message( + id: !eventLog.chatMessage!.messageId.isEmpty ? eventLog.chatMessage!.messageId : UUID().uuidString, + status: statusTmp, + isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, + dateReceived: eventLog.chatMessage?.time ?? 0, + address: addressCleaned?.asStringUriOnly() ?? "", + isFirstMessage: isFirstMessageTmp, + text: contentText, + attachmentsNames: attachmentNameList, + attachments: attachmentList, + replyMessage: replyMessageTmp, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp + ) ), at: 0 ) self.addChatMessageDelegate(message: eventLog.chatMessage!) } else { conversationMessagesTmp.insert( - Message( - id: UUID().uuidString, - status: nil, - isOutgoing: false, - dateReceived: 0, - address: "", - isFirstMessage: false, - text: "", - attachments: [], - ownReaction: "", - reactions: [] + EventLogMessage( + eventLog: eventLog, + message: Message( + id: UUID().uuidString, + status: nil, + isOutgoing: false, + dateReceived: 0, + address: "", + isFirstMessage: false, + text: "", + attachments: [], + ownReaction: "", + reactions: [] + ) ), at: 0 ) } @@ -996,8 +1032,8 @@ class ConversationViewModel: ObservableObject { if !conversationMessagesTmp.isEmpty { DispatchQueue.main.async { - if self.conversationMessagesSection[0].rows.last?.address == conversationMessagesTmp.last?.address { - self.conversationMessagesSection[0].rows[self.conversationMessagesSection[0].rows.count - 1].isFirstMessage = false + if self.conversationMessagesSection[0].rows.last?.message.address == conversationMessagesTmp.last?.message.address { + self.conversationMessagesSection[0].rows[self.conversationMessagesSection[0].rows.count - 1].message.isFirstMessage = false } self.conversationMessagesSection[0].rows.append(contentsOf: conversationMessagesTmp.reversed()) @@ -1013,13 +1049,14 @@ class ConversationViewModel: ObservableObject { } } } + // swiftlint:enable cyclomatic_complexity func sendMessage() { coreContext.doOnCoreQueue { _ in do { var message: ChatMessage? if self.messageToReply != nil { - let chatMessageToReply = self.displayedConversation!.chatRoom.findMessage(messageId: self.messageToReply!.id) + let chatMessageToReply = self.messageToReply!.eventLog.chatMessage if chatMessageToReply != nil { message = try self.displayedConversation!.chatRoom.createReplyMessage(message: chatMessageToReply!) } @@ -1264,8 +1301,8 @@ class ConversationViewModel: ObservableObject { func sendReaction(emoji: String) { coreContext.doOnCoreQueue { _ in if self.selectedMessage != nil { - Log.info("[ConversationViewModel] Sending reaction \(emoji) to message with ID \(self.selectedMessage!.id)") - let messageToSendReaction = self.displayedConversation!.chatRoom.findMessage(messageId: self.selectedMessage!.id) + Log.info("[ConversationViewModel] Sending reaction \(emoji) to message with ID \(self.selectedMessage!.message.id)") + let messageToSendReaction = self.selectedMessage!.eventLog.chatMessage if messageToSendReaction != nil { do { let reaction = try messageToSendReaction!.createReaction(utf8Reaction: messageToSendReaction?.ownReaction?.body == emoji ? "" : emoji) @@ -1275,12 +1312,12 @@ class ConversationViewModel: ObservableObject { DispatchQueue.main.async { if indexMessageSelected != nil { - self.conversationMessagesSection[0].rows[indexMessageSelected!].ownReaction = messageToSendReaction?.ownReaction?.body == emoji ? "" : emoji + self.conversationMessagesSection[0].rows[indexMessageSelected!].message.ownReaction = messageToSendReaction?.ownReaction?.body == emoji ? "" : emoji } self.selectedMessage = nil } } catch { - Log.info("[ConversationViewModel] Error: Can't send reaction \(emoji) to message with ID \(self.selectedMessage!.id)") + Log.info("[ConversationViewModel] Error: Can't send reaction \(emoji) to message with ID \(self.selectedMessage!.message.id)") } } } @@ -1289,28 +1326,11 @@ class ConversationViewModel: ObservableObject { func resend() { coreContext.doOnCoreQueue { _ in - if self.selectedMessage != nil { - Log.info("[ConversationViewModel] Re-sending message with ID \(self.selectedMessage!.id)") - let messageToResend = self.displayedConversation!.chatRoom.findMessage(messageId: self.selectedMessage!.id) - if messageToResend != nil { - messageToResend!.send() - } + if self.selectedMessage != nil && self.selectedMessage!.eventLog.chatMessage != nil { + Log.info("[ConversationViewModel] Re-sending message with ID \(self.selectedMessage!.eventLog.chatMessage!)") + self.selectedMessage!.eventLog.chatMessage!.send() } } } } -struct LinphoneCustomEventLog: Hashable { - var id = UUID() - var eventLog: EventLog - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - -extension LinphoneCustomEventLog { - static func ==(lhs: LinphoneCustomEventLog, rhs: LinphoneCustomEventLog) -> Bool { - return lhs.id == rhs.id - } -} // swiftlint:enable type_body_length