diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index bb19caad9..676c6caed 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -92,6 +92,7 @@ final class CoreContext: ObservableObject { path: "\(configDir)/linphonerc", factoryPath: Bundle.main.path(forResource: "linphonerc-factory", ofType: nil) ) + if config != nil { self.mCore = try? Factory.Instance.createCoreWithConfig(config: config!, systemContext: nil) } diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index e1e8ef98b..f0c10a683 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -95,10 +95,24 @@ struct ChatBubbleView: View { } VStack { - Text(message.text - ) - .foregroundStyle(Color.grayMain2c700) - .default_text_style(styleSize: 16) + if !message.attachments.isEmpty { + AsyncImage(url: message.attachments.first!.full) { image in + image.resizable() + .scaledToFill() + //.aspectRatio(1.5, contentMode: .fill) + //.clipped() + } placeholder: { + ProgressView() + } + .frame(maxHeight: 400) + //.frame(width: 50, height: 50) + } + + if !message.text.isEmpty { + Text(message.text) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 16) + } } .padding(.all, 15) .background(message.isOutgoing ? Color.orangeMain100 : Color.grayMain2c100) diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index 22fb9ede6..a1090a043 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -215,7 +215,6 @@ class ConversationModel: ObservableObject { } } - func getUnreadMessagesCount() { coreContext.doOnCoreQueue { _ in self.unreadMessagesCount = self.chatRoom.unreadMessagesCount @@ -242,6 +241,13 @@ class ConversationModel: ObservableObject { } } + func downloadContent(chatMessage: ChatMessage, content: Content) { + coreContext.doOnCoreQueue { _ in + let result = chatMessage.downloadContent(content: content) + print("resultresult download \(result)") + } + } + func deleteChatRoom() { CoreContext.shared.doOnCoreQueue { core in core.deleteChatRoom(chatRoom: self.chatRoom) diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index 3865cf26c..ecc65b40f 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -66,15 +66,16 @@ public struct Message: Identifiable, Hashable { public var recording: Recording? public var replyMessage: ReplyMessage? - public init(id: String, - status: Status? = nil, - createdAt: Date = Date(), - isOutgoing: Bool, - text: String = "", - attachments: [Attachment] = [], - recording: Recording? = nil, - replyMessage: ReplyMessage? = nil) { - + public init( + id: String, + status: Status? = nil, + createdAt: Date = Date(), + isOutgoing: Bool, + text: String = "", + attachments: [Attachment] = [], + recording: Recording? = nil, + replyMessage: ReplyMessage? = nil + ) { self.id = id self.status = status self.createdAt = createdAt diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 50cf29a2a..b711fd279 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -21,6 +21,7 @@ import Foundation import linphonesw import Combine import SwiftUI +import AVFoundation class ConversationViewModel: ObservableObject { @@ -113,10 +114,30 @@ class ConversationViewModel: ObservableObject { DispatchQueue.main.async { self.conversationMessagesList.append(LinphoneCustomEventLog(eventLog: eventLog)) } + + var attachmentList: [Attachment] = [] + var contentText = "" + + if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { + eventLog.chatMessage!.contents.forEach { content in + if content.isText { + contentText = content.utf8Text ?? "" + } else { + if content.filePath == nil || content.filePath!.isEmpty { + self.downloadContent(chatMessage: eventLog.chatMessage!, content: content) + } else { + let attachment = Attachment(id: UUID().uuidString, url: URL(string: "file://" + content.filePath!)!, type: .image) + attachmentList.append(attachment) + } + } + } + } + conversationMessage.append(Message( id: UUID().uuidString, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - text: eventLog.chatMessage?.utf8Text ?? "")) + text: contentText, + attachments: attachmentList)) DispatchQueue.main.async { if index == historyEvents.count - 1 { @@ -133,28 +154,29 @@ class ConversationViewModel: ObservableObject { coreContext.doOnCoreQueue { _ in if self.displayedConversation != nil { let historyEvents = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: self.conversationMessagesList.count, end: self.conversationMessagesList.count + 30) - - //For List - /* - historyEvents.reversed().forEach { eventLog in - DispatchQueue.main.async { - self.conversationMessagesList.append(LinphoneCustomEventLog(eventLog: eventLog)) - } - } - */ - - //For ScrollView var conversationMessagesListTmp: [LinphoneCustomEventLog] = [] var conversationMessagesTmp: [Message] = [] historyEvents.reversed().forEach { eventLog in conversationMessagesListTmp.insert(LinphoneCustomEventLog(eventLog: eventLog), at: 0) + var attachmentList: [Attachment] = [] + var contentText = "" + + if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { + eventLog.chatMessage!.contents.forEach { content in + if content.isText { + contentText = content.utf8Text ?? "" + } + } + } + conversationMessagesTmp.insert( Message( id: UUID().uuidString, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - text: eventLog.chatMessage?.utf8Text ?? "" + text: contentText, + attachments: attachmentList ), at: 0 ) } @@ -162,8 +184,6 @@ class ConversationViewModel: ObservableObject { if !conversationMessagesTmp.isEmpty { DispatchQueue.main.async { self.conversationMessagesList.insert(contentsOf: conversationMessagesListTmp, at: 0) - //self.conversationMessagesSection.append(MessagesSection(date: Date(), rows: conversationMessagesTmp.reversed())) - //self.conversationMessagesIds.append(UUID().uuidString) self.conversationMessagesSection[0].rows.append(contentsOf: conversationMessagesTmp.reversed()) } } @@ -175,26 +195,28 @@ class ConversationViewModel: ObservableObject { var conversationMessage: [Message] = [] eventLogs.enumerated().forEach { index, eventLog in DispatchQueue.main.async { - //withAnimation { - //For List - //self.conversationMessagesList.insert(LinphoneCustomEventLog(eventLog: eventLog), at: 0) - - //For ScrollView self.conversationMessagesList.append(LinphoneCustomEventLog(eventLog: eventLog)) - - /* - conversationMessage.append(Message( - id: UUID().uuidString, - isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - text: eventLog.chatMessage?.utf8Text ?? "" - ) - ) - */ } + + var attachmentList: [Attachment] = [] + var contentText = "" + + if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty { + eventLog.chatMessage!.contents.forEach { content in + if content.isText { + print("contentscontents text") + contentText = content.utf8Text ?? "" + } else { + print("contentscontents \(content.isText)") + } + } + } + let message = Message( id: UUID().uuidString, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, - text: eventLog.chatMessage?.utf8Text ?? "" + text: contentText, + attachments: attachmentList ) DispatchQueue.main.async { @@ -309,6 +331,38 @@ class ConversationViewModel: ObservableObject { func changeDisplayedChatRoom(conversationModel: ConversationModel) { self.displayedConversation = conversationModel } + + func downloadContent(chatMessage: ChatMessage, content: Content) { + //Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)") + if content.filePath == nil || content.filePath!.isEmpty { + let contentName = content.name + if contentName != nil { + let isImage = FileUtil.isExtensionImage(path: contentName!) + let file = FileUtil.getFileStoragePath(fileName: contentName!, isImage: isImage) + content.filePath = file + Log.info( + "[ConversationViewModel] File \(contentName) will be downloaded at \(content.filePath)" + ) + self.displayedConversation?.downloadContent(chatMessage: chatMessage, content: content) + } else { + Log.error("[ConversationViewModel] Content name is null, can't download it!") + } + } + } + + func generateThumbnail(path: URL) -> UIImage? { + do { + 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) + return thumbnail + } catch let error { + print("*** Error generating thumbnail: \(error.localizedDescription)") + return nil + } + } } struct LinphoneCustomEventLog: Hashable { var id = UUID() diff --git a/Linphone/Utils/FileUtils.swift b/Linphone/Utils/FileUtils.swift index d923462ed..97dc886e1 100644 --- a/Linphone/Utils/FileUtils.swift +++ b/Linphone/Utils/FileUtils.swift @@ -19,8 +19,19 @@ import UIKit import linphonesw +import UniformTypeIdentifiers class FileUtil: NSObject { + + public enum MimeType { + case plainText + case pdf + case image + case video + case audio + case unknown + } + public class func bundleFilePath(_ file: NSString) -> String? { return Bundle.main.path(forResource: file.deletingPathExtension, ofType: file.pathExtension) } @@ -82,7 +93,7 @@ class FileUtil: NSObject { return false } } - + public class func write(string: String, toPath: String) { do { try string.write(to: URL(fileURLWithPath: toPath), atomically: true, encoding: String.Encoding.utf8) @@ -122,4 +133,99 @@ class FileUtil: NSObject { } } + public class func isExtensionImage(path: String) -> Bool { + let extensionName = getExtensionFromFileName(fileName: path) + let typeExtension = getMimeTypeFromExtension(urlString: extensionName) + return getMimeType(type: typeExtension) == MimeType.image + } + + public class func getExtensionFromFileName(fileName: String) -> String { + let url: URL? = URL(string: fileName) + let urlExtension: String? = url?.pathExtension + + return urlExtension?.lowercased() ?? "" + } + + public class func getMimeTypeFromExtension(urlString: String?) -> String? { + if urlString == nil || urlString!.isEmpty { + return nil + } + + return urlString!.mimeType() + } + + public class func getMimeType(type: String?) -> MimeType { + if type == nil || type!.isEmpty { + return MimeType.unknown + } + + switch type { + case let str where str!.starts(with: "image/"): + return MimeType.image + case let str where str!.starts(with: "text/"): + return MimeType.plainText + case let str where str!.starts(with: "/log"): + return MimeType.plainText + case let str where str!.starts(with: "video/"): + return MimeType.video + case let str where str!.starts(with: "audio/"): + return MimeType.audio + case let str where str!.starts(with: "application/pdf"): + return MimeType.pdf + default: + return MimeType.unknown + } + } + + public class func getFileStoragePath( + fileName: String, + isImage: Bool = false, + overrideExisting: Bool = false + ) -> String { + return getFileStorageDir(fileName: fileName, isPicture: isImage) + } + + public class func getFileStorageDir(fileName: String, isPicture: Bool = false) -> String { + return Factory.Instance.getDownloadDir(context: nil) + fileName + } +} + +extension NSURL { + public func mimeType() -> String { + if let pathExt = self.pathExtension, + let mimeType = UTType(filenameExtension: pathExt)?.preferredMIMEType { + return mimeType + } + else { + return "application/octet-stream" + } + } +} + +extension URL { + public func mimeType() -> String { + if let mimeType = UTType(filenameExtension: self.pathExtension)?.preferredMIMEType { + return mimeType + } + else { + return "application/octet-stream" + } + } +} + +extension NSString { + public func mimeType() -> String { + if let mimeType = UTType(filenameExtension: self.pathExtension)?.preferredMIMEType { + return mimeType + } + else { + return "application/octet-stream" + } + } +} + +extension String { + public func mimeType() -> String { + return (self as NSString).mimeType() + } }