From c6c0793b592a46ff58e74a2dbd625b684b68918c Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 19 Nov 2024 15:59:49 +0100 Subject: [PATCH] Fix content list in message bubble --- .../Fragments/ChatBubbleView.swift | 185 ++++++++++++------ .../UI/Main/Conversations/Model/Message.swift | 6 +- .../ViewModel/ConversationViewModel.swift | 45 +++-- 3 files changed, 157 insertions(+), 79 deletions(-) diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index e4ccc2acf..efaf4fe1d 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -641,6 +641,16 @@ struct ChatBubbleView: View { } .frame(width: 100, height: 100) .background(Color.grayMain2c200) + .onTapGesture { + if eventLogMessage.message.attachments.first!.type == .fileTransfer && !eventLogMessage.message.isFileTransferInProgress { + CoreContext.shared.doOnCoreQueue { _ in + conversationViewModel.downloadContent( + chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!, + content: eventLogMessage.eventModel.eventLog.chatMessage!.contents.first! + ) + } + } + } VStack { Text(eventLogMessage.message.attachments.first!.name) @@ -658,7 +668,6 @@ struct ChatBubbleView: View { .padding(.horizontal, 10) .frame(maxWidth: .infinity, alignment: .leading) } - .frame(width: geometryProxy.size.width - 110, height: 100) .background(.white) .clipShape(RoundedRectangle(cornerRadius: 10)) .onTapGesture { @@ -666,74 +675,126 @@ struct ChatBubbleView: View { } } } 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(eventLogMessage.message.attachments, id: \.id) { attachment in - ZStack { - Rectangle() - .fill(Color(.white)) - .frame(width: 120, height: 120) - - if #available(iOS 16.0, *) { - AsyncImage(url: attachment.thumbnail) { image in - ZStack { - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - - if attachment.type == .video { - Image("play-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) + let sizeCard = ((geometryProxy.size.width - 150)/2)-2 + let columns = [ + GridItem(.adaptive(minimum: sizeCard), spacing: 1)] + + LazyVStack { + LazyVGrid(columns: columns) { + ForEach(eventLogMessage.message.attachments, id: \.id) { attachment in + if attachment.type == .image || attachment.type == .gif + || attachment.type == .video { + 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) + + if attachment.type == .video { + Image("play-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 40, height: 40, alignment: .leading) + } + } + } placeholder: { + ProgressView() + } + .layoutPriority(-1) + .onTapGesture { + selectedURLAttachment = attachment.full + } + } else { + AsyncImage(url: attachment.thumbnail) { image in + ZStack { + image + .resizable() + .interpolation(.medium) + .aspectRatio(contentMode: .fill) + + if attachment.type == .video { + Image("play-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 40, height: 40, alignment: .leading) + } + } + } placeholder: { + ProgressView() + } + .id(UUID()) + .layoutPriority(-1) + .onTapGesture { + selectedURLAttachment = attachment.full } } - } placeholder: { - ProgressView() - } - .layoutPriority(-1) - .onTapGesture { - selectedURLAttachment = attachment.full - } - } else { - AsyncImage(url: attachment.thumbnail) { image in - ZStack { - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - - if attachment.type == .video { - Image("play-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) - } - } - } 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 { + 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 + ) + } + } + } + } + + 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 } } - .clipShape(RoundedRectangle(cornerRadius: 4)) - .contentShape(Rectangle()) } } - .frame( 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 * eventLogMessage.message.attachments.count) - ) + .frame(width: geometryProxy.size.width - 150) } } diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index ec035c713..9f43634df 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -88,6 +88,8 @@ public struct Message: Identifiable, Hashable { public var isIcalendar: Bool public var messageConferenceInfo: MessageConferenceInfo? + public var isFileTransferInProgress: Bool + public init( id: String, appData: String = "", @@ -109,7 +111,8 @@ public struct Message: Identifiable, Hashable { ephemeralExpireTime: Int = 0, ephemeralLifetime: Int = 0, isIcalendar: Bool = false, - messageConferenceInfo: MessageConferenceInfo? = nil + messageConferenceInfo: MessageConferenceInfo? = nil, + isFileTransferInProgress: Bool = false ) { self.id = id self.appData = appData @@ -132,6 +135,7 @@ public struct Message: Identifiable, Hashable { self.ephemeralLifetime = ephemeralLifetime self.isIcalendar = isIcalendar self.messageConferenceInfo = messageConferenceInfo + self.isFileTransferInProgress = isFileTransferInProgress } public static func makeMessage( diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index efb321009..6bcdb0982 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -590,7 +590,8 @@ 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 + 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 ) ) ) @@ -823,7 +824,8 @@ 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 + 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 ) ), at: 0 ) @@ -1069,7 +1071,8 @@ 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 + 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 ) ) @@ -1363,7 +1366,8 @@ 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 + 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 ) ), at: 0 ) @@ -1608,18 +1612,27 @@ class ConversationViewModel: ObservableObject { func downloadContent(chatMessage: ChatMessage, content: Content) { // Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)") - if !chatMessage.isFileTransferInProgress && (content.filePath == nil || content.filePath!.isEmpty) { - if let contentName = content.name { - // let isImage = FileUtil.isExtensionImage(path: contentName) - let file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + (contentName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") - // let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage) - content.filePath = String(file.dropFirst(7)) - Log.info( - "[ConversationViewModel] File \(contentName) will be downloaded at \(content.filePath ?? "NIL")" - ) - self.displayedConversation?.downloadContent(chatMessage: chatMessage, content: content) - } else { - Log.error("[ConversationViewModel] Content name is null, can't download it!") + if self.displayedConversation != nil { + if !chatMessage.isFileTransferInProgress && (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) ?? "") + // let file = FileUtil.getFileStoragePath(fileName: contentName ?? "", isImage: isImage) + + var counter = 1 + while FileManager.default.fileExists(atPath: file) { + file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + "\(counter)_" + (contentName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") + counter += 1 + } + + content.filePath = String(file.dropFirst(7)) + Log.info( + "[ConversationViewModel] File \(contentName) will be downloaded at \(content.filePath ?? "NIL")" + ) + self.displayedConversation!.downloadContent(chatMessage: chatMessage, content: content) + } else { + Log.error("[ConversationViewModel] Content name is null, can't download it!") + } } } }