diff --git a/ConversationsClassic/AppCore/Actions/FileActions.swift b/ConversationsClassic/AppCore/Actions/FileActions.swift index 9acb339..5e44717 100644 --- a/ConversationsClassic/AppCore/Actions/FileActions.swift +++ b/ConversationsClassic/AppCore/Actions/FileActions.swift @@ -1,10 +1,10 @@ import Foundation enum FileAction: Stateable { - case downloadAttachmentFile(id: String, attachmentRemotePath: URL) - case attachmentFileDownloaded(id: String, localUrl: URL) - case downloadingAttachmentFileFailed(id: String, reason: String) + case downloadAttachmentFile(messageId: String, attachmentRemotePath: URL) + case attachmentFileDownloaded(messageId: String, localName: String) + case downloadingAttachmentFileFailed(messageId: String, reason: String) - case createAttachmentThumbnail(id: String, localUrl: URL) - case attachmentThumbnailCreated(id: String, thumbnailUrl: URL) + case createAttachmentThumbnail(messageId: String, localName: String) + case attachmentThumbnailCreated(messageId: String, thumbnailName: String) } diff --git a/ConversationsClassic/AppCore/Database/Database+Migrations.swift b/ConversationsClassic/AppCore/Database/Database+Migrations.swift index 339e473..7b0c2d6 100644 --- a/ConversationsClassic/AppCore/Database/Database+Migrations.swift +++ b/ConversationsClassic/AppCore/Database/Database+Migrations.swift @@ -59,9 +59,9 @@ extension Database { table.column("pending", .boolean).notNull() table.column("sentError", .boolean).notNull() table.column("attachmentType", .integer) - table.column("attachmentLocalPath", .text) + table.column("attachmentLocalName", .text) table.column("attachmentRemotePath", .text) - table.column("attachmentThumbnailPath", .text) + table.column("attachmentThumbnailName", .text) table.column("attachmentDownloadFailed", .boolean).notNull().defaults(to: false) } } diff --git a/ConversationsClassic/AppCore/Files/FileProcessing.swift b/ConversationsClassic/AppCore/Files/FileProcessing.swift index b1e7bc5..6c6f102 100644 --- a/ConversationsClassic/AppCore/Files/FileProcessing.swift +++ b/ConversationsClassic/AppCore/Files/FileProcessing.swift @@ -14,19 +14,18 @@ final class FileProcessing { return subdirectoryURL } - func createThumbnail(localUrl: URL) -> URL? { - let fileExtension = localUrl.pathExtension - let fileNameWithoutExtension = localUrl.deletingPathExtension().lastPathComponent - let thumbnailFileName = fileNameWithoutExtension + "_thumb." + fileExtension + func createThumbnail(localName: String) -> String? { + let thumbnailFileName = "thumb_\(localName)" let thumbnailUrl = FileProcessing.fileFolder.appendingPathComponent(thumbnailFileName) + let localUrl = FileProcessing.fileFolder.appendingPathComponent(localName) // check if thumbnail already exists if FileManager.default.fileExists(atPath: thumbnailUrl.path) { - return thumbnailUrl + return thumbnailFileName } // create thumbnail if not exists - switch localUrl.lastPathComponent.attachmentType { + switch localName.attachmentType { case .image: guard let image = UIImage(contentsOfFile: localUrl.path) else { return nil } let targetSize = CGSize(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) @@ -34,7 +33,7 @@ final class FileProcessing { guard let data = thumbnail.pngData() else { return nil } do { try data.write(to: thumbnailUrl) - return thumbnailUrl + return thumbnailFileName } catch { return nil } diff --git a/ConversationsClassic/AppCore/Middlewares/DatabaseMiddleware.swift b/ConversationsClassic/AppCore/Middlewares/DatabaseMiddleware.swift index 4c3abac..837f3fc 100644 --- a/ConversationsClassic/AppCore/Middlewares/DatabaseMiddleware.swift +++ b/ConversationsClassic/AppCore/Middlewares/DatabaseMiddleware.swift @@ -35,7 +35,7 @@ final class DatabaseMiddleware { .store(in: &cancellables) } - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity func middleware(state _: AppState, action: AppAction) -> AnyPublisher { switch action { // MARK: Accounts @@ -309,7 +309,7 @@ final class DatabaseMiddleware { } .eraseToAnyPublisher() - case .fileAction(.attachmentFileDownloaded(let id, let localUrl)): + case .fileAction(.attachmentFileDownloaded(let id, let localName)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { @@ -321,7 +321,7 @@ final class DatabaseMiddleware { _ = try database._db.write { db in try Message .filter(Column("id") == id) - .updateAll(db, Column("attachmentLocalPath").set(to: localUrl), Column("attachmentDownloadFailed").set(to: false)) + .updateAll(db, Column("attachmentLocalName").set(to: localName), Column("attachmentDownloadFailed").set(to: false)) } promise(.success(.empty)) } catch { @@ -332,7 +332,7 @@ final class DatabaseMiddleware { } .eraseToAnyPublisher() - case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailUrl)): + case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailName)): return Future { promise in Task(priority: .background) { [weak self] in guard let database = self?.database else { @@ -344,7 +344,7 @@ final class DatabaseMiddleware { _ = try database._db.write { db in try Message .filter(Column("id") == id) - .updateAll(db, Column("attachmentThumbnailPath").set(to: thumbnailUrl)) + .updateAll(db, Column("attachmentThumbnailName").set(to: thumbnailName)) } promise(.success(.empty)) } catch { diff --git a/ConversationsClassic/AppCore/Middlewares/FileMiddleware.swift b/ConversationsClassic/AppCore/Middlewares/FileMiddleware.swift index f32f993..dcd5eab 100644 --- a/ConversationsClassic/AppCore/Middlewares/FileMiddleware.swift +++ b/ConversationsClassic/AppCore/Middlewares/FileMiddleware.swift @@ -21,7 +21,7 @@ final class FileMiddleware { wSelf.downloadingMessageIDs.insert(message.id) DispatchQueue.main.async { // swiftlint:disable:next force_unwrapping - store.dispatch(.fileAction(.downloadAttachmentFile(id: message.id, attachmentRemotePath: message.attachmentRemotePath!))) + store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: message.attachmentRemotePath!))) } } promise(.success(.empty)) @@ -29,31 +29,32 @@ final class FileMiddleware { case .fileAction(.downloadAttachmentFile(let id, let attachmentRemotePath)): return Future { promise in - let localUrl = FileProcessing.fileFolder.appendingPathComponent(id).appendingPathExtension(attachmentRemotePath.pathExtension) + let localName = "\(id)_\(UUID().uuidString)\(attachmentRemotePath.lastPathComponent)" + let localUrl = FileProcessing.fileFolder.appendingPathComponent(localName) DownloadManager.shared.enqueueDownload(from: attachmentRemotePath, to: localUrl) { error in DispatchQueue.main.async { if let error { - store.dispatch(.fileAction(.downloadingAttachmentFileFailed(id: id, reason: error.localizedDescription))) + store.dispatch(.fileAction(.downloadingAttachmentFileFailed(messageId: id, reason: error.localizedDescription))) } else { - store.dispatch(.fileAction(.attachmentFileDownloaded(id: id, localUrl: localUrl))) + store.dispatch(.fileAction(.attachmentFileDownloaded(messageId: id, localName: localName))) } } } promise(.success(.empty)) }.eraseToAnyPublisher() - case .fileAction(.attachmentFileDownloaded(let id, let localUrl)): + case .fileAction(.attachmentFileDownloaded(let id, let localName)): return Future { [weak self] promise in self?.downloadingMessageIDs.remove(id) - promise(.success(.fileAction(.createAttachmentThumbnail(id: id, localUrl: localUrl)))) + promise(.success(.fileAction(.createAttachmentThumbnail(messageId: id, localName: localName)))) } .eraseToAnyPublisher() - case .fileAction(.createAttachmentThumbnail(let id, let localUrl)): + case .fileAction(.createAttachmentThumbnail(let id, let localName)): return Future { [weak self] promise in - if let thumbnailUrl = FileProcessing.shared.createThumbnail(localUrl: localUrl) { + if let thumbnailName = FileProcessing.shared.createThumbnail(localName: localName) { self?.downloadingMessageIDs.remove(id) - promise(.success(.fileAction(.attachmentThumbnailCreated(id: id, thumbnailUrl: thumbnailUrl)))) + promise(.success(.fileAction(.attachmentThumbnailCreated(messageId: id, thumbnailName: thumbnailName)))) } else { self?.downloadingMessageIDs.remove(id) promise(.success(.empty)) diff --git a/ConversationsClassic/AppCore/Models/Message.swift b/ConversationsClassic/AppCore/Models/Message.swift index 5f5eab1..4e1dc30 100644 --- a/ConversationsClassic/AppCore/Models/Message.swift +++ b/ConversationsClassic/AppCore/Models/Message.swift @@ -42,9 +42,9 @@ struct Message: DBStorable, Equatable { let sentError: Bool var attachmentType: MessageAttachmentType? - var attachmentLocalPath: URL? + var attachmentLocalName: String? var attachmentRemotePath: URL? - var attachmentThumbnailPath: URL? + var attachmentThumbnailName: String? var attachmentDownloadFailed: Bool = false } @@ -96,9 +96,9 @@ extension Message { pending: false, sentError: false, attachmentType: nil, - attachmentLocalPath: nil, + attachmentLocalName: nil, attachmentRemotePath: nil, - attachmentThumbnailPath: nil, + attachmentThumbnailName: nil, attachmentDownloadFailed: false ) if let oob = martinMessage.oob { @@ -108,3 +108,15 @@ extension Message { return msg } } + +extension Message { + var attachmentLocalPath: URL? { + guard let attachmentLocalName = attachmentLocalName else { return nil } + return FileProcessing.fileFolder.appendingPathComponent(attachmentLocalName) + } + + var attachmentThumbnailPath: URL? { + guard let attachmentThumbnailName = attachmentThumbnailName else { return nil } + return FileProcessing.fileFolder.appendingPathComponent(attachmentThumbnailName) + } +} diff --git a/ConversationsClassic/View/Screens/Conversation/ConversationMessageContainer.swift b/ConversationsClassic/View/Screens/Conversation/ConversationMessageContainer.swift index 599e4c4..0a1687b 100644 --- a/ConversationsClassic/View/Screens/Conversation/ConversationMessageContainer.swift +++ b/ConversationsClassic/View/Screens/Conversation/ConversationMessageContainer.swift @@ -88,8 +88,6 @@ private struct AttachmentView: View { if let file = message.attachmentLocalPath { VideoPlayerView(url: file) .frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize) - .cornerRadius(Const.attachmentPreviewSize / 10) - .overlay(RoundedRectangle(cornerRadius: Const.attachmentPreviewSize / 10).stroke(Color.Material.Shape.separator, lineWidth: 1)) } else { placeholder } @@ -135,7 +133,7 @@ private struct AttachmentView: View { } .onTapGesture { if let url = message.attachmentRemotePath { - store.dispatch(.fileAction(.downloadAttachmentFile(id: message.id, attachmentRemotePath: url))) + store.dispatch(.fileAction(.downloadAttachmentFile(messageId: message.id, attachmentRemotePath: url))) } } } @@ -160,12 +158,14 @@ private struct AttachmentView: View { } } +// TODO: Make video player better! private struct VideoPlayerView: UIViewControllerRepresentable { let url: URL func makeUIViewController(context _: Context) -> AVPlayerViewController { let controller = AVPlayerViewController() controller.player = AVPlayer(url: url) + controller.allowsPictureInPicturePlayback = true return controller }