From 1763f21359cfa9c0698822107ca244ee96c220e4 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Fri, 22 Nov 2024 16:57:32 +0100 Subject: [PATCH] Fix media list rendering in chat bubble --- Linphone/Localizable.xcstrings | 3 + .../Fragments/ChatBubbleView.swift | 313 +++++++++------ .../Fragments/ConversationFragment.swift | 350 +++++++++-------- .../Main/Conversations/Model/Attachment.swift | 14 +- .../UI/Main/Conversations/Model/Message.swift | 8 +- .../ViewModel/ConversationViewModel.swift | 369 ++++++++++++------ 6 files changed, 627 insertions(+), 430 deletions(-) diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index d58c4e71b..39112271b 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -123,6 +123,9 @@ }, "%lld selected participants" : { + }, + "%lld%%" : { + }, "+" : { diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index efaf4fe1d..427e36ca0 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -538,68 +538,48 @@ struct ChatBubbleView: View { if eventLogMessage.message.attachments.first!.type == .image || eventLogMessage.message.attachments.first!.type == .video { if #available(iOS 16.0, *) { - AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in - switch phase { - case .empty: - ProgressView() - case .success(let image): - ZStack { - image + CachedAsyncImage( + url: eventLogMessage.message.attachments.first!.thumbnail, + placeholder: ProgressView(), + onImageTapped: { + selectedURLAttachment = eventLogMessage.message.attachments.first!.full + }) + + .overlay( + Group { + if eventLogMessage.message.attachments.first!.type == .video { + Image("play-fill") + .renderingMode(.template) .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - - if eventLogMessage.message.attachments.first!.type == .video { - Image("play-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) - } + .foregroundStyle(.white) + .frame(width: 40, height: 40) } - case .failure: - Image("image-broken") - @unknown default: - EmptyView() } - } + ) .layoutPriority(-1) .clipShape(RoundedRectangle(cornerRadius: 4)) - .onTapGesture { - selectedURLAttachment = eventLogMessage.message.attachments.first!.full - } } else { - AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in - switch phase { - case .empty: - ProgressView() - case .success(let image): - ZStack { - image + CachedAsyncImage( + url: eventLogMessage.message.attachments.first!.thumbnail, + placeholder: ProgressView(), + onImageTapped: { + selectedURLAttachment = eventLogMessage.message.attachments.first!.full + }) + + .overlay( + Group { + if eventLogMessage.message.attachments.first!.type == .video { + Image("play-fill") + .renderingMode(.template) .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - - if eventLogMessage.message.attachments.first!.type == .video { - Image("play-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) - } + .foregroundStyle(.white) + .frame(width: 40, height: 40) } - case .failure: - Image("image-broken") - @unknown default: - EmptyView() } - } + ) .layoutPriority(-1) .clipShape(RoundedRectangle(cornerRadius: 4)) .id(UUID()) - .onTapGesture { - selectedURLAttachment = eventLogMessage.message.attachments.first!.full - } } } else if eventLogMessage.message.attachments.first!.type == .gif { if #available(iOS 16.0, *) { @@ -633,22 +613,29 @@ struct ChatBubbleView: View { } else { HStack { VStack { - Image(getImageOfType(type: eventLogMessage.message.attachments.first!.type)) - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c700) - .frame(width: 60, height: 60, alignment: .leading) + if conversationViewModel.attachmentTransferInProgress != nil && conversationViewModel.attachmentTransferInProgress!.id == eventLogMessage.message.attachments.first!.id { + CircularProgressView(progress: Double(conversationViewModel.attachmentTransferInProgress!.transferProgressIndication) / 100.0) + .frame(width: 80, height: 80) + } else { + Image(getImageOfType(type: eventLogMessage.message.attachments.first!.type)) + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c700) + .frame(width: 60, height: 60, alignment: .leading) + } } .frame(width: 100, height: 100) .background(Color.grayMain2c200) .onTapGesture { - if eventLogMessage.message.attachments.first!.type == .fileTransfer && !eventLogMessage.message.isFileTransferInProgress { + if eventLogMessage.message.attachments.first!.type == .fileTransfer && eventLogMessage.message.attachments.first!.transferProgressIndication == -1 { CoreContext.shared.doOnCoreQueue { _ in conversationViewModel.downloadContent( chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!, content: eventLogMessage.eventModel.eventLog.chatMessage!.contents.first! ) } + } else { + selectedURLAttachment = eventLogMessage.message.attachments.first!.full } } @@ -676,121 +663,117 @@ struct ChatBubbleView: View { } } else if eventLogMessage.message.attachments.count > 1 { let sizeCard = ((geometryProxy.size.width - 150)/2)-2 - let columns = [ - GridItem(.adaptive(minimum: sizeCard), spacing: 1)] + let columns = [GridItem(.adaptive(minimum: sizeCard), spacing: 1)] - LazyVStack { + VStack { LazyVGrid(columns: columns) { - ForEach(eventLogMessage.message.attachments, id: \.id) { attachment in - if attachment.type == .image || attachment.type == .gif - || attachment.type == .video { + ForEach(eventLogMessage.message.attachments.filter({ $0.type == .image || $0.type == .gif + || $0.type == .video }), id: \.id) { attachment in ZStack { Rectangle() .fill(Color(.white)) .frame(width: sizeCard, height: sizeCard) if #available(iOS 16.0, *) { - AsyncImage(url: attachment.thumbnail) { image in - ZStack { - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - + CachedAsyncImage( + url: attachment.thumbnail, + placeholder: ProgressView(), + onImageTapped: { + selectedURLAttachment = attachment.full + }) + + .overlay( + Group { if attachment.type == .video { Image("play-fill") .renderingMode(.template) .resizable() .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) + .frame(width: 40, height: 40) } } - } placeholder: { - ProgressView() - } + ) .layoutPriority(-1) - .onTapGesture { - selectedURLAttachment = attachment.full - } } else { - AsyncImage(url: attachment.thumbnail) { image in - ZStack { - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - + CachedAsyncImage( + url: attachment.thumbnail, + placeholder: ProgressView(), + onImageTapped: { + selectedURLAttachment = attachment.full + }) + + .overlay( + Group { if attachment.type == .video { Image("play-fill") .renderingMode(.template) .resizable() .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) + .frame(width: 40, height: 40) } } - } placeholder: { - ProgressView() - } + ) .id(UUID()) .layoutPriority(-1) - .onTapGesture { - selectedURLAttachment = attachment.full - } } } .clipShape(RoundedRectangle(cornerRadius: 4)) .contentShape(Rectangle()) } - } } - ForEach(eventLogMessage.message.attachments, id: \.id) { attachment in - if !(attachment.type == .image || attachment.type == .gif - || attachment.type == .video) { - HStack { - VStack { + ForEach(eventLogMessage.message.attachments.filter({ $0.type != .image && $0.type != .gif + && $0.type != .video }), id: \.id) { attachment in + HStack { + VStack { + if conversationViewModel.attachmentTransferInProgress != nil && conversationViewModel.attachmentTransferInProgress!.id == attachment.id { + CircularProgressView(progress: Double(conversationViewModel.attachmentTransferInProgress!.transferProgressIndication) / 100.0) + .frame(width: 80, height: 80) + } else { Image(getImageOfType(type: attachment.type)) .renderingMode(.template) .resizable() .foregroundStyle(Color.grayMain2c700) .frame(width: 60, height: 60, alignment: .leading) } - .frame(width: 100, height: 100) - .background(Color.grayMain2c200) - .onTapGesture { - if attachment.type == .fileTransfer && !eventLogMessage.message.isFileTransferInProgress { - if let content = eventLogMessage.eventModel.eventLog.chatMessage!.contents.first(where: {$0.filePath == attachment.full.absoluteString}) { - CoreContext.shared.doOnCoreQueue { _ in - conversationViewModel.downloadContent( - chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!, - content: content - ) - } + } + .frame(width: 100, height: 100) + .background(Color.grayMain2c200) + .onTapGesture { + if attachment.type == .fileTransfer && attachment.transferProgressIndication == -1 { + CoreContext.shared.doOnCoreQueue { _ in + if let content = eventLogMessage.eventModel.eventLog.chatMessage!.contents.first(where: {$0.name == attachment.name}) { + conversationViewModel.downloadContent( + chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!, + content: content + ) } } + } else { + selectedURLAttachment = attachment.full } + } + + VStack { + Text(attachment.name) + .foregroundStyle(Color.grayMain2c700) + .default_text_style_600(styleSize: 14) + .truncationMode(.middle) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) - VStack { - Text(attachment.name) - .foregroundStyle(Color.grayMain2c700) - .default_text_style_600(styleSize: 14) - .truncationMode(.middle) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - - Text(attachment.size.formatBytes()) - .default_text_style_300(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - } - .padding(.horizontal, 10) - .frame(maxWidth: .infinity, alignment: .leading) - } - .background(.white) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .onTapGesture { - selectedURLAttachment = attachment.full + Text(attachment.size.formatBytes()) + .default_text_style_300(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) } + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, alignment: .leading) + } + .background(.white) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .onTapGesture { + selectedURLAttachment = attachment.full } } } @@ -1083,6 +1066,82 @@ struct CustomSlider: View { } } +struct CircularProgressView: View { + var progress: Double + + var body: some View { + ZStack { + Circle() + .stroke(Color(.systemGray4), lineWidth: 5) + Circle() + .trim(from: 0, to: progress) + .stroke( + Color.orangeMain500, + style: StrokeStyle(lineWidth: 5, lineCap: .round)) + .rotationEffect(Angle(degrees: -90)) + .animation(.easeInOut(duration: 0.5), value: progress) + .overlay( + Text("\(Int(progress * 100))%") + .font(.system(size: 15, weight: .bold, design: .rounded)) + .foregroundColor(Color.orangeMain500) + ) + } + .padding() + } +} + +class ImageCache { + static let shared = NSCache() +} + +struct CachedAsyncImage: View { + let url: URL + let placeholder: Placeholder + let onImageTapped: (() -> Void)? + + @State private var image: UIImage? + + var body: some View { + ZStack { + if let image = image { + Image(uiImage: image) + .resizable() + .interpolation(.medium) + .aspectRatio(contentMode: .fill) + .onTapGesture { + onImageTapped?() + } + } else { + placeholder + .onAppear { + loadImage() + } + } + } + } + + private func loadImage() { + if let cachedImage = ImageCache.shared.object(forKey: url as NSURL) { + self.image = cachedImage + return + } + + Task { + do { + let (data, _) = try await URLSession.shared.data(from: url) + if let downloadedImage = UIImage(data: data) { + ImageCache.shared.setObject(downloadedImage, forKey: url as NSURL) + await MainActor.run { + self.image = downloadedImage + } + } + } catch { + print("Error loading image: \(error.localizedDescription)") + } + } + } +} + /* #Preview { ChatBubbleView(conversationViewModel: ConversationViewModel(), index: 0) diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 28a5911a2..94358cef9 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -757,139 +757,115 @@ struct ConversationFragment: View { if conversationViewModel.selectedMessage != nil && conversationViewModel.displayedConversation != nil { let iconSize = ((geometry.size.width - (conversationViewModel.displayedConversation!.isGroup ? 43 : 10) - 10) / 6) - 30 - VStack { - Spacer() - + + ScrollView { VStack { - HStack { - if conversationViewModel.selectedMessage!.message.isOutgoing { - Spacer() + Spacer() + + VStack { + HStack { + if conversationViewModel.selectedMessage!.message.isOutgoing { + Spacer() + } + + HStack { + Button { + conversationViewModel.sendReaction(emoji: "👍") + } label: { + Text("👍") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) + } + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white) + .cornerRadius(10) + + Button { + conversationViewModel.sendReaction(emoji: "❤️") + } label: { + Text("❤️") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) + } + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white) + .cornerRadius(10) + + Button { + conversationViewModel.sendReaction(emoji: "😂") + } label: { + Text("😂") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) + } + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white) + .cornerRadius(10) + + Button { + conversationViewModel.sendReaction(emoji: "😮") + } label: { + Text("😮") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) + } + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white) + .cornerRadius(10) + + Button { + conversationViewModel.sendReaction(emoji: "😢") + } label: { + Text("😢") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) + } + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white) + .cornerRadius(10) + + /* + Button { + } label: { + Image("plus-circle") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: iconSize > 50 ? 50 : iconSize, height: iconSize > 50 ? 50 : iconSize, alignment: .leading) + } + .padding(.trailing, 5) + */ + } + .padding(.vertical, 5) + .padding(.horizontal, 10) + .background(.white) + .cornerRadius(20) + + if !conversationViewModel.selectedMessage!.message.isOutgoing { + Spacer() + } } + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) + .shadow(color: .black.opacity(0.1), radius: 10) + + ChatBubbleView(conversationViewModel: conversationViewModel, eventLogMessage: conversationViewModel.selectedMessage!, geometryProxy: geometry) + .padding(.horizontal, 10) + .padding(.vertical, 1) + .shadow(color: .black.opacity(0.1), radius: 10) HStack { - Button { - conversationViewModel.sendReaction(emoji: "👍") - } label: { - Text("👍") - .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) - } - .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white) - .cornerRadius(10) - - Button { - conversationViewModel.sendReaction(emoji: "❤️") - } label: { - Text("❤️") - .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) - } - .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white) - .cornerRadius(10) - - Button { - conversationViewModel.sendReaction(emoji: "😂") - } label: { - Text("😂") - .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) - } - .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white) - .cornerRadius(10) - - Button { - conversationViewModel.sendReaction(emoji: "😮") - } label: { - Text("😮") - .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) - } - .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white) - .cornerRadius(10) - - Button { - conversationViewModel.sendReaction(emoji: "😢") - } label: { - Text("😢") - .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) - } - .padding(.horizontal, 8) - .background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white) - .cornerRadius(10) - - /* - Button { - } label: { - Image("plus-circle") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: iconSize > 50 ? 50 : iconSize, height: iconSize > 50 ? 50 : iconSize, alignment: .leading) - } - .padding(.trailing, 5) - */ - } - .padding(.vertical, 5) - .padding(.horizontal, 10) - .background(.white) - .cornerRadius(20) - - if !conversationViewModel.selectedMessage!.message.isOutgoing { - Spacer() - } - } - .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) - .shadow(color: .black.opacity(0.1), radius: 10) - - 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!.message.isOutgoing { - Spacer() - } - - VStack { - Button { - let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id}) - conversationViewModel.selectedMessage = nil - conversationViewModel.replyToMessage(index: indexMessage ?? 0) - } label: { - HStack { - Text("menu_reply_to_chat_message") - .default_text_style(styleSize: 15) - Spacer() - Image("reply") - .resizable() - .frame(width: 20, height: 20, alignment: .leading) - } - .padding(.vertical, 5) - .padding(.horizontal, 20) + if conversationViewModel.selectedMessage!.message.isOutgoing { + Spacer() } - Divider() - - if !conversationViewModel.selectedMessage!.message.text.isEmpty { + VStack { Button { - UIPasteboard.general.setValue( - conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available", - forPasteboardType: UTType.plainText.identifier - ) - - ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard" - ToastViewModel.shared.displayToast = true - + let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id}) conversationViewModel.selectedMessage = nil + conversationViewModel.replyToMessage(index: indexMessage ?? 0) } label: { HStack { - Text("menu_copy_chat_message") + Text("menu_reply_to_chat_message") .default_text_style(styleSize: 15) Spacer() - Image("copy") + Image("reply") .resizable() .frame(width: 20, height: 20, alignment: .leading) } @@ -898,65 +874,93 @@ struct ConversationFragment: View { } Divider() + + if !conversationViewModel.selectedMessage!.message.text.isEmpty { + Button { + UIPasteboard.general.setValue( + conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available", + forPasteboardType: UTType.plainText.identifier + ) + + ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard" + ToastViewModel.shared.displayToast = true + + conversationViewModel.selectedMessage = nil + } label: { + HStack { + Text("menu_copy_chat_message") + .default_text_style(styleSize: 15) + Spacer() + Image("copy") + .resizable() + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) + } + + Divider() + } + + Button { + conversationForwardMessageViewModel.initConversationsLists(convsList: conversationsListViewModel.conversationsListTmp) + conversationForwardMessageViewModel.selectedMessage = conversationViewModel.selectedMessage + conversationViewModel.selectedMessage = nil + withAnimation { + isShowConversationForwardMessageFragment = true + } + } label: { + HStack { + Text("menu_forward_chat_message") + .default_text_style(styleSize: 15) + Spacer() + Image("forward") + .resizable() + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) + } + + Divider() + + Button { + conversationViewModel.deleteMessage() + } label: { + HStack { + Text("menu_delete_selected_item") + .foregroundStyle(.red) + .default_text_style(styleSize: 15) + Spacer() + Image("trash-simple-red") + .renderingMode(.template) + .resizable() + .foregroundStyle(.red) + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) + } } + .frame(maxWidth: geometry.size.width / 1.5) + .padding(.vertical, 8) + .background(.white) + .cornerRadius(20) - Button { - conversationForwardMessageViewModel.initConversationsLists(convsList: conversationsListViewModel.conversationsListTmp) - conversationForwardMessageViewModel.selectedMessage = conversationViewModel.selectedMessage - conversationViewModel.selectedMessage = nil - withAnimation { - isShowConversationForwardMessageFragment = true - } - } label: { - HStack { - Text("menu_forward_chat_message") - .default_text_style(styleSize: 15) - Spacer() - Image("forward") - .resizable() - .frame(width: 20, height: 20, alignment: .leading) - } - .padding(.vertical, 5) - .padding(.horizontal, 20) - } - - Divider() - - Button { - conversationViewModel.deleteMessage() - } label: { - HStack { - Text("menu_delete_selected_item") - .foregroundStyle(.red) - .default_text_style(styleSize: 15) - Spacer() - Image("trash-simple-red") - .renderingMode(.template) - .resizable() - .foregroundStyle(.red) - .frame(width: 20, height: 20, alignment: .leading) - } - .padding(.vertical, 5) - .padding(.horizontal, 20) + if !conversationViewModel.selectedMessage!.message.isOutgoing { + Spacer() } } - .frame(maxWidth: geometry.size.width / 1.5) - .padding(.vertical, 8) - .background(.white) - .cornerRadius(20) - - if !conversationViewModel.selectedMessage!.message.isOutgoing { - Spacer() - } + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .padding(.bottom, 20) + .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) + .shadow(color: .black.opacity(0.1), radius: 10) } - .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.bottom, 20) - .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) - .shadow(color: .black.opacity(0.1), radius: 10) } + .frame(maxWidth: .infinity) + .frame(minHeight: geometry.size.height) } - .frame(maxWidth: .infinity) .background(.gray.opacity(0.1)) .onTapGesture { withAnimation { diff --git a/Linphone/UI/Main/Conversations/Model/Attachment.swift b/Linphone/UI/Main/Conversations/Model/Attachment.swift index 58b0536bb..16ed30a06 100644 --- a/Linphone/UI/Main/Conversations/Model/Attachment.swift +++ b/Linphone/UI/Main/Conversations/Model/Attachment.swift @@ -85,8 +85,9 @@ public struct Attachment: Codable, Identifiable, Hashable { public let type: AttachmentType public let duration: Int public let size: Int + public var transferProgressIndication: Int - public init(id: String, name: String, thumbnail: URL, full: URL, type: AttachmentType, duration: Int = 0, size: Int = 0) { + public init(id: String, name: String, thumbnail: URL, full: URL, type: AttachmentType, duration: Int = 0, size: Int = 0, transferProgressIndication: Int = 0) { self.id = id self.name = name self.thumbnail = thumbnail @@ -94,9 +95,16 @@ public struct Attachment: Codable, Identifiable, Hashable { self.type = type self.duration = duration self.size = size + self.transferProgressIndication = transferProgressIndication } - public init(id: String, name: String, url: URL, type: AttachmentType, duration: Int = 0, size: Int = 0) { - self.init(id: id, name: name, thumbnail: url, full: url, type: type, duration: duration, size: size) + public init(id: String, name: String, url: URL, type: AttachmentType, duration: Int = 0, size: Int = 0, transferProgressIndication: Int = 0) { + self.init(id: id, name: name, thumbnail: url, full: url, type: type, duration: duration, size: size, transferProgressIndication: transferProgressIndication) + } +} + +extension Attachment: Equatable { + public static func == (lhs: Attachment, rhs: Attachment) -> Bool { + lhs.id == rhs.id && lhs.transferProgressIndication == rhs.transferProgressIndication } } diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index 9f43634df..48a36e7a1 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -88,8 +88,6 @@ public struct Message: Identifiable, Hashable { public var isIcalendar: Bool public var messageConferenceInfo: MessageConferenceInfo? - public var isFileTransferInProgress: Bool - public init( id: String, appData: String = "", @@ -111,8 +109,7 @@ public struct Message: Identifiable, Hashable { ephemeralExpireTime: Int = 0, ephemeralLifetime: Int = 0, isIcalendar: Bool = false, - messageConferenceInfo: MessageConferenceInfo? = nil, - isFileTransferInProgress: Bool = false + messageConferenceInfo: MessageConferenceInfo? = nil ) { self.id = id self.appData = appData @@ -135,7 +132,6 @@ public struct Message: Identifiable, Hashable { self.ephemeralLifetime = ephemeralLifetime self.isIcalendar = isIcalendar self.messageConferenceInfo = messageConferenceInfo - self.isFileTransferInProgress = isFileTransferInProgress } public static func makeMessage( @@ -188,7 +184,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.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 } } diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index dbc6ea773..f29239936 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -104,6 +104,7 @@ class ConversationViewModel: ObservableObject { @Published var progress: Double = 0.0 @Published var attachments: [Attachment] = [] + @Published var attachmentTransferInProgress: Attachment? struct SheetCategory: Identifiable { let id = UUID() @@ -165,115 +166,231 @@ class ConversationViewModel: ObservableObject { } func addChatMessageDelegate(message: ChatMessage) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if self.displayedConversation != nil { - var statusTmp: Message.Status? = .sending - switch message.state { - case .InProgress: - statusTmp = .sending - case .Delivered: - statusTmp = .sent - case .DeliveredToUser: - statusTmp = .received - case .Displayed: - statusTmp = .read - case .NotDelivered: - statusTmp = .error - default: - statusTmp = .sending + if self.displayedConversation != nil { + var statusTmp: Message.Status? = .sending + switch message.state { + case .InProgress: + statusTmp = .sending + case .Delivered: + statusTmp = .sent + case .DeliveredToUser: + statusTmp = .received + case .Displayed: + statusTmp = .read + case .NotDelivered: + statusTmp = .error + default: + statusTmp = .sending + } + + let ephemeralExpireTimeTmp = message.ephemeralExpireTime + + if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty { + if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) { + self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId } - let ephemeralExpireTimeTmp = message.ephemeralExpireTime - - if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty { - if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) { - self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId - } - - if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) { - if indexMessage < self.conversationMessagesSection[0].rows.count { - if self.conversationMessagesSection[0].rows[indexMessage].message.status != statusTmp { - DispatchQueue.main.async { - self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error + if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) { + if indexMessage < self.conversationMessagesSection[0].rows.count { + if self.conversationMessagesSection[0].rows[indexMessage].message.status != statusTmp { + DispatchQueue.main.async { + self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error + self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp + } + } else { + DispatchQueue.main.async { + if indexMessage < self.conversationMessagesSection[0].rows.count { self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp } - } else { - DispatchQueue.main.async { - if indexMessage < self.conversationMessagesSection[0].rows.count { - self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp + } + } + } + } + } + + self.coreContext.doOnCoreQueue { _ in + let chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in + var statusTmp: Message.Status? + switch message.state { + case .InProgress: + statusTmp = .sending + case .Delivered: + statusTmp = .sent + case .DeliveredToUser: + statusTmp = .received + case .Displayed: + statusTmp = .read + case .NotDelivered: + statusTmp = .error + default: + statusTmp = .sending + } + + if msgState == .FileTransferDone { + message.contents.forEach { content in + if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) { + if let contentIndex = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.firstIndex(where: {$0.name == content.name && $0.full.pathExtension.isEmpty && $0.full.absoluteString != content.filePath}) { + let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/") + let contentTmp = self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex] + if let pathThumbnail = content.type == "video" ? URL(string: self.generateThumbnail(name: content.name ?? "")) : contentTmp.thumbnail { + if let path = URL(string: self.getNewFilePath(name: filePathSep[1])) { + if path != contentTmp.full { + + 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 + case "video": + typeTmp = .video + default: + typeTmp = .other + } + + let newAttachment = Attachment( + id: UUID().uuidString, + name: content.name ?? contentTmp.name, + thumbnail: content.type == "video" ? pathThumbnail : path, + full: path, + type: typeTmp, + duration: contentTmp.duration, + size: contentTmp.size, + transferProgressIndication: 100 + ) + + DispatchQueue.main.async { + self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex] = newAttachment + } + } + } } } } } } - } - - self.coreContext.doOnCoreQueue { _ in - let chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in - var statusTmp: Message.Status? - switch message.state { - case .InProgress: - statusTmp = .sending - case .Delivered: - statusTmp = .sent - case .DeliveredToUser: - statusTmp = .received - case .Displayed: - statusTmp = .read - case .NotDelivered: - statusTmp = .error - default: - statusTmp = .sending - } - - if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) { - self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId - } - - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) - - DispatchQueue.main.async { - if indexMessage != nil { - self.conversationMessagesSection[0].rows[indexMessage!].message.status = statusTmp ?? .error - } - } - }, onNewMessageReaction: { (message: ChatMessage, _: ChatMessageReaction) in - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) - var reactionsTmp: [String] = [] - message.reactions.forEach({ chatMessageReaction in - reactionsTmp.append(chatMessageReaction.body) - }) - - DispatchQueue.main.async { - if indexMessage != nil { - self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp - } - } - }, onReactionRemoved: { (message: ChatMessage, _: Address) in - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) - var reactionsTmp: [String] = [] - message.reactions.forEach({ chatMessageReaction in - reactionsTmp.append(chatMessageReaction.body) - }) - - DispatchQueue.main.async { - if indexMessage != nil { - self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp - } - } - }, onEphemeralMessageTimerStarted: { (message: ChatMessage) in - let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) - let ephemeralExpireTimeTmp = message.ephemeralExpireTime - - DispatchQueue.main.async { - if indexMessage != nil { - self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp - } + + if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) { + self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId + } + + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.status = statusTmp ?? .error } + } + }, onNewMessageReaction: { (message: ChatMessage, _: ChatMessageReaction) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + var reactionsTmp: [String] = [] + message.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) }) - self.chatMessageDelegateHolders.append(ChatMessageDelegateHolder(message: message, delegate: chatMessageDelegate)) - } + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp + } + } + }, onReactionRemoved: { (message: ChatMessage, _: Address) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + var reactionsTmp: [String] = [] + message.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp + } + } + }, onFileTransferProgressIndication: { (message: ChatMessage, content: Content, offset: Int, total: Int) in + if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) { + + let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/") + let path = URL(string: self.getNewFilePath(name: filePathSep[1])) + + if let contentTmp = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.first(where: {$0.full == path}) { + DispatchQueue.main.async { + self.attachmentTransferInProgress = contentTmp + self.attachmentTransferInProgress!.transferProgressIndication = ((offset * 100) / total) + } + + if ((offset * 100) / total) >= 100 { + DispatchQueue.main.async { + self.attachmentTransferInProgress = nil + } + } + /* + if ((offset * 100) / total) >= 100 { + + 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 + case "video": + typeTmp = .video + default: + typeTmp = .other + } + + if let pathThumbnail = content.type == "video" ? URL(string: self.generateThumbnail(name: content.name ?? "")) : contentTmp.thumbnail { + if content.filePath != nil { + let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/") + if let path = URL(string: self.getNewFilePath(name: filePathSep[1])) { + + let newAttachment = Attachment( + id: UUID().uuidString, + name: content.name ?? contentTmp.name, + thumbnail: content.type == "video" ? pathThumbnail : path, + full: path, + type: typeTmp, + duration: contentTmp.duration, + size: contentTmp.size, + transferProgressIndication: 100 + ) + + if let contentIndex = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.firstIndex(where: {$0.full == path}) { + DispatchQueue.main.async { + print("attachmentattachment ----------------") + print("attachmentattachment ---------------- \(content.name ?? "")") + print("attachmentattachment ---------------- \(filePathSep[1])") + print("attachmentattachment ----------------") + self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex] = newAttachment + } + } + } + } + } + } + */ + } + } + }, onEphemeralMessageTimerStarted: { (message: ChatMessage) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + let ephemeralExpireTimeTmp = message.ephemeralExpireTime + + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp + } + } + }) + + self.chatMessageDelegateHolders.append(ChatMessageDelegateHolder(message: message, delegate: chatMessageDelegate)) } } } @@ -425,7 +542,8 @@ class ConversationViewModel: ObservableObject { name: content.name!, url: path!, type: .fileTransfer, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -458,7 +576,8 @@ class ConversationViewModel: ObservableObject { url: path!, type: typeTmp, duration: typeTmp == .voiceRecording ? content.fileDuration : 0, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -481,7 +600,8 @@ class ConversationViewModel: ObservableObject { thumbnail: pathThumbnail!, full: path!, type: .video, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -595,8 +715,7 @@ class ConversationViewModel: ObservableObject { ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0, ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0, isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false, - messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil, - isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress + messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil ) ) ) @@ -648,7 +767,7 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { eventLog.chatMessage!.contents.forEach { content in - if content.isText { + if content.isText && content.name == nil { contentText = content.utf8Text ?? "" } else if content.name != nil && !content.name!.isEmpty { if content.filePath == nil || content.filePath!.isEmpty { @@ -662,7 +781,8 @@ class ConversationViewModel: ObservableObject { name: content.name!, url: path!, type: .fileTransfer, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -694,7 +814,8 @@ class ConversationViewModel: ObservableObject { url: path!, type: typeTmp, duration: typeTmp == . voiceRecording ? content.fileDuration : 0, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -717,7 +838,8 @@ class ConversationViewModel: ObservableObject { thumbnail: pathThumbnail!, full: path!, type: .video, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -831,8 +953,7 @@ class ConversationViewModel: ObservableObject { ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0, ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0, isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false, - messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil, - isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress + messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil ) ), at: 0 ) @@ -882,7 +1003,7 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { eventLog.chatMessage!.contents.forEach { content in - if content.isText { + if content.isText && content.name == nil { contentText = content.utf8Text ?? "" } else { if content.filePath == nil || content.filePath!.isEmpty { @@ -896,7 +1017,8 @@ class ConversationViewModel: ObservableObject { name: content.name ?? "???", url: path!, type: .fileTransfer, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name ?? "???")" attachmentList.append(attachment) @@ -905,6 +1027,7 @@ class ConversationViewModel: ObservableObject { 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 { @@ -928,7 +1051,8 @@ class ConversationViewModel: ObservableObject { url: path!, type: typeTmp, duration: typeTmp == . voiceRecording ? content.fileDuration : 0, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -951,7 +1075,8 @@ class ConversationViewModel: ObservableObject { thumbnail: pathThumbnail!, full: path!, type: .video, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -1059,6 +1184,7 @@ class ConversationViewModel: ObservableObject { } if eventLog.chatMessage != nil { + let message = EventLogMessage( eventModel: EventModel(eventLog: eventLog), message: Message( @@ -1080,8 +1206,7 @@ class ConversationViewModel: ObservableObject { ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0, ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0, isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false, - messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil, - isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress + messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil ) ) @@ -1194,7 +1319,7 @@ class ConversationViewModel: ObservableObject { if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { eventLog.chatMessage!.contents.forEach { content in - if content.isText { + if content.isText && content.name == nil { contentText = content.utf8Text ?? "" } else if content.name != nil && !content.name!.isEmpty { if content.filePath == nil || content.filePath!.isEmpty { @@ -1208,7 +1333,8 @@ class ConversationViewModel: ObservableObject { name: content.name!, url: path!, type: .fileTransfer, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -1240,7 +1366,8 @@ class ConversationViewModel: ObservableObject { url: path!, type: typeTmp, duration: typeTmp == . voiceRecording ? content.fileDuration : 0, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -1263,7 +1390,8 @@ class ConversationViewModel: ObservableObject { thumbnail: pathThumbnail!, full: path!, type: .video, - size: content.fileSize + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 ) attachmentNameList += ", \(content.name!)" attachmentList.append(attachment) @@ -1377,8 +1505,7 @@ class ConversationViewModel: ObservableObject { ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0, ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0, isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false, - messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil, - isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress + messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil ) ), at: 0 ) @@ -1624,7 +1751,7 @@ class ConversationViewModel: ObservableObject { func downloadContent(chatMessage: ChatMessage, content: Content) { // Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)") if self.displayedConversation != nil { - if !chatMessage.isFileTransferInProgress && (content.filePath == nil || content.filePath!.isEmpty) { + if content.filePath == nil || content.filePath!.isEmpty { if let contentName = content.name { // let isImage = FileUtil.isExtensionImage(path: contentName) var file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + (contentName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")