From 0682489645ef19f361869b4b4e2aba376181cc2c Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 21 May 2024 10:29:22 +0200 Subject: [PATCH] Add video preview in message bubble --- .../play-fill.imageset/Contents.json | 21 +++++++ .../play-fill.imageset/play-fill.svg | 1 + .../Fragments/ChatBubbleView.swift | 56 ++++++++++++------- .../ViewModel/ConversationViewModel.swift | 51 ++++++++++++++++- 4 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 Linphone/Assets.xcassets/play-fill.imageset/Contents.json create mode 100644 Linphone/Assets.xcassets/play-fill.imageset/play-fill.svg diff --git a/Linphone/Assets.xcassets/play-fill.imageset/Contents.json b/Linphone/Assets.xcassets/play-fill.imageset/Contents.json new file mode 100644 index 000000000..3d4192c3e --- /dev/null +++ b/Linphone/Assets.xcassets/play-fill.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "play-fill.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/play-fill.imageset/play-fill.svg b/Linphone/Assets.xcassets/play-fill.imageset/play-fill.svg new file mode 100644 index 000000000..fb0a6d7fb --- /dev/null +++ b/Linphone/Assets.xcassets/play-fill.imageset/play-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index 557637086..d4f18a7c6 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -160,7 +160,7 @@ struct ChatBubbleView: View { @ViewBuilder func messageAttachments() -> some View { if message.attachments.count == 1 { - if message.attachments.first!.type == .image || message.attachments.first!.type == .gif { + if message.attachments.first!.type == .image || message.attachments.first!.type == .gif || message.attachments.first!.type == .video { let result = imageDimensions(url: message.attachments.first!.full.absoluteString) ZStack { Rectangle() @@ -185,12 +185,22 @@ struct ChatBubbleView: View { ) } - if message.attachments.first!.type == .image { + if message.attachments.first!.type == .image || message.attachments.first!.type == .video { AsyncImage(url: message.attachments.first!.full) { image in - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) + ZStack { + image + .resizable() + .interpolation(.medium) + .aspectRatio(contentMode: .fill) + + if message.attachments.first!.type == .video { + Image("play-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 40, height: 40, alignment: .leading) + } + } } placeholder: { ProgressView() } @@ -212,26 +222,34 @@ struct ChatBubbleView: View { GridItem(.adaptive(minimum: 120), spacing: 1) ], spacing: 3) { ForEach(message.attachments) { attachment in - if attachment.type == .image || attachment.type == .gif { - ZStack { - Rectangle() - .fill(Color(.white)) - .frame(width: 120, height: 120) - - AsyncImage(url: attachment.full) { image in + ZStack { + Rectangle() + .fill(Color(.white)) + .frame(width: 120, height: 120) + + AsyncImage(url: attachment.full) { image in + ZStack { image .resizable() .interpolation(.medium) .aspectRatio(contentMode: .fill) - } placeholder: { - ProgressView() + + if attachment.type == .video { + Image("play-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 40, height: 40, alignment: .leading) + } } - .id(UUID()) - .layoutPriority(-1) + } placeholder: { + ProgressView() } - .clipShape(RoundedRectangle(cornerRadius: 4)) - .clipped() + .id(UUID()) + .layoutPriority(-1) } + .clipShape(RoundedRectangle(cornerRadius: 4)) + .clipped() } } .frame( diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index f34753f70..7dcefdf2f 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -127,9 +127,28 @@ class ConversationViewModel: ObservableObject { if content.filePath == nil || content.filePath!.isEmpty { self.downloadContent(chatMessage: eventLog.chatMessage!, content: content) } else { - if URL(string: self.getNewFilePath(name: content.name ?? "")) != nil { - let attachment = Attachment(id: UUID().uuidString, url: URL(string: self.getNewFilePath(name: content.name ?? ""))!, type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image) - attachmentList.append(attachment) + if content.type != "video" { + let path = URL(string: self.getNewFilePath(name: content.name ?? "")) + if path != nil { + let attachment = + Attachment( + id: UUID().uuidString, + url: path!, + type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image + ) + attachmentList.append(attachment) + } + } else if content.type == "video" { + let path = URL(string: self.generateThumbnail(name: content.name ?? "")) + if path != nil { + let attachment = + Attachment( + id: UUID().uuidString, + url: path!, + type: .video + ) + attachmentList.append(attachment) + } } } } @@ -483,6 +502,32 @@ class ConversationViewModel: ObservableObject { return "file://" + Factory.Instance.getDownloadDir(context: nil) + (name.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") } + func generateThumbnail(name: String) -> String { + do { + let path = URL(string: "file://" + Factory.Instance.getDownloadDir(context: nil) + (name.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")) + let asset = AVURLAsset(url: path!, options: nil) + let imgGenerator = AVAssetImageGenerator(asset: asset) + imgGenerator.appliesPreferredTrackTransform = true + let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) + let thumbnail = UIImage(cgImage: cgImage) + + guard let data = thumbnail.jpegData(compressionQuality: 1) ?? thumbnail.pngData() else { + return "" + } + + let urlName = URL(string: "file://" + Factory.Instance.getDownloadDir(context: nil) + "preview_" + (name.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") + ".png") + + if urlName != nil { + let decodedData: () = try data.write(to: urlName!) + } + + return urlName!.absoluteString + } catch let error { + print("*** Error generating thumbnail: \(error.localizedDescription)") + return "" + } + } + func getMessageTime(startDate: time_t) -> String { let timeInterval = TimeInterval(startDate)