wip
This commit is contained in:
parent
14a83ca1d8
commit
9c5c54e09e
|
@ -1,10 +1,10 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum FileAction: Stateable {
|
enum FileAction: Stateable {
|
||||||
case downloadAttachmentFile(id: String, attachmentRemotePath: URL)
|
case downloadAttachmentFile(messageId: String, attachmentRemotePath: URL)
|
||||||
case attachmentFileDownloaded(id: String, localUrl: URL)
|
case attachmentFileDownloaded(messageId: String, localName: String)
|
||||||
case downloadingAttachmentFileFailed(id: String, reason: String)
|
case downloadingAttachmentFileFailed(messageId: String, reason: String)
|
||||||
|
|
||||||
case createAttachmentThumbnail(id: String, localUrl: URL)
|
case createAttachmentThumbnail(messageId: String, localName: String)
|
||||||
case attachmentThumbnailCreated(id: String, thumbnailUrl: URL)
|
case attachmentThumbnailCreated(messageId: String, thumbnailName: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,9 @@ extension Database {
|
||||||
table.column("pending", .boolean).notNull()
|
table.column("pending", .boolean).notNull()
|
||||||
table.column("sentError", .boolean).notNull()
|
table.column("sentError", .boolean).notNull()
|
||||||
table.column("attachmentType", .integer)
|
table.column("attachmentType", .integer)
|
||||||
table.column("attachmentLocalPath", .text)
|
table.column("attachmentLocalName", .text)
|
||||||
table.column("attachmentRemotePath", .text)
|
table.column("attachmentRemotePath", .text)
|
||||||
table.column("attachmentThumbnailPath", .text)
|
table.column("attachmentThumbnailName", .text)
|
||||||
table.column("attachmentDownloadFailed", .boolean).notNull().defaults(to: false)
|
table.column("attachmentDownloadFailed", .boolean).notNull().defaults(to: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,18 @@ final class FileProcessing {
|
||||||
return subdirectoryURL
|
return subdirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
func createThumbnail(localUrl: URL) -> URL? {
|
func createThumbnail(localName: String) -> String? {
|
||||||
let fileExtension = localUrl.pathExtension
|
let thumbnailFileName = "thumb_\(localName)"
|
||||||
let fileNameWithoutExtension = localUrl.deletingPathExtension().lastPathComponent
|
|
||||||
let thumbnailFileName = fileNameWithoutExtension + "_thumb." + fileExtension
|
|
||||||
let thumbnailUrl = FileProcessing.fileFolder.appendingPathComponent(thumbnailFileName)
|
let thumbnailUrl = FileProcessing.fileFolder.appendingPathComponent(thumbnailFileName)
|
||||||
|
let localUrl = FileProcessing.fileFolder.appendingPathComponent(localName)
|
||||||
|
|
||||||
// check if thumbnail already exists
|
// check if thumbnail already exists
|
||||||
if FileManager.default.fileExists(atPath: thumbnailUrl.path) {
|
if FileManager.default.fileExists(atPath: thumbnailUrl.path) {
|
||||||
return thumbnailUrl
|
return thumbnailFileName
|
||||||
}
|
}
|
||||||
|
|
||||||
// create thumbnail if not exists
|
// create thumbnail if not exists
|
||||||
switch localUrl.lastPathComponent.attachmentType {
|
switch localName.attachmentType {
|
||||||
case .image:
|
case .image:
|
||||||
guard let image = UIImage(contentsOfFile: localUrl.path) else { return nil }
|
guard let image = UIImage(contentsOfFile: localUrl.path) else { return nil }
|
||||||
let targetSize = CGSize(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
let targetSize = CGSize(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
||||||
|
@ -34,7 +33,7 @@ final class FileProcessing {
|
||||||
guard let data = thumbnail.pngData() else { return nil }
|
guard let data = thumbnail.pngData() else { return nil }
|
||||||
do {
|
do {
|
||||||
try data.write(to: thumbnailUrl)
|
try data.write(to: thumbnailUrl)
|
||||||
return thumbnailUrl
|
return thumbnailFileName
|
||||||
} catch {
|
} catch {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ final class DatabaseMiddleware {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
||||||
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
|
||||||
switch action {
|
switch action {
|
||||||
// MARK: Accounts
|
// MARK: Accounts
|
||||||
|
@ -309,7 +309,7 @@ final class DatabaseMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
case .fileAction(.attachmentFileDownloaded(let id, let localUrl)):
|
case .fileAction(.attachmentFileDownloaded(let id, let localName)):
|
||||||
return Future<AppAction, Never> { promise in
|
return Future<AppAction, Never> { promise in
|
||||||
Task(priority: .background) { [weak self] in
|
Task(priority: .background) { [weak self] in
|
||||||
guard let database = self?.database else {
|
guard let database = self?.database else {
|
||||||
|
@ -321,7 +321,7 @@ final class DatabaseMiddleware {
|
||||||
_ = try database._db.write { db in
|
_ = try database._db.write { db in
|
||||||
try Message
|
try Message
|
||||||
.filter(Column("id") == id)
|
.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))
|
promise(.success(.empty))
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -332,7 +332,7 @@ final class DatabaseMiddleware {
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailUrl)):
|
case .fileAction(.attachmentThumbnailCreated(let id, let thumbnailName)):
|
||||||
return Future<AppAction, Never> { promise in
|
return Future<AppAction, Never> { promise in
|
||||||
Task(priority: .background) { [weak self] in
|
Task(priority: .background) { [weak self] in
|
||||||
guard let database = self?.database else {
|
guard let database = self?.database else {
|
||||||
|
@ -344,7 +344,7 @@ final class DatabaseMiddleware {
|
||||||
_ = try database._db.write { db in
|
_ = try database._db.write { db in
|
||||||
try Message
|
try Message
|
||||||
.filter(Column("id") == id)
|
.filter(Column("id") == id)
|
||||||
.updateAll(db, Column("attachmentThumbnailPath").set(to: thumbnailUrl))
|
.updateAll(db, Column("attachmentThumbnailName").set(to: thumbnailName))
|
||||||
}
|
}
|
||||||
promise(.success(.empty))
|
promise(.success(.empty))
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -21,7 +21,7 @@ final class FileMiddleware {
|
||||||
wSelf.downloadingMessageIDs.insert(message.id)
|
wSelf.downloadingMessageIDs.insert(message.id)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
// swiftlint:disable:next force_unwrapping
|
// 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))
|
promise(.success(.empty))
|
||||||
|
@ -29,31 +29,32 @@ final class FileMiddleware {
|
||||||
|
|
||||||
case .fileAction(.downloadAttachmentFile(let id, let attachmentRemotePath)):
|
case .fileAction(.downloadAttachmentFile(let id, let attachmentRemotePath)):
|
||||||
return Future { promise in
|
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
|
DownloadManager.shared.enqueueDownload(from: attachmentRemotePath, to: localUrl) { error in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if let error {
|
if let error {
|
||||||
store.dispatch(.fileAction(.downloadingAttachmentFileFailed(id: id, reason: error.localizedDescription)))
|
store.dispatch(.fileAction(.downloadingAttachmentFileFailed(messageId: id, reason: error.localizedDescription)))
|
||||||
} else {
|
} else {
|
||||||
store.dispatch(.fileAction(.attachmentFileDownloaded(id: id, localUrl: localUrl)))
|
store.dispatch(.fileAction(.attachmentFileDownloaded(messageId: id, localName: localName)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
promise(.success(.empty))
|
promise(.success(.empty))
|
||||||
}.eraseToAnyPublisher()
|
}.eraseToAnyPublisher()
|
||||||
|
|
||||||
case .fileAction(.attachmentFileDownloaded(let id, let localUrl)):
|
case .fileAction(.attachmentFileDownloaded(let id, let localName)):
|
||||||
return Future { [weak self] promise in
|
return Future { [weak self] promise in
|
||||||
self?.downloadingMessageIDs.remove(id)
|
self?.downloadingMessageIDs.remove(id)
|
||||||
promise(.success(.fileAction(.createAttachmentThumbnail(id: id, localUrl: localUrl))))
|
promise(.success(.fileAction(.createAttachmentThumbnail(messageId: id, localName: localName))))
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
case .fileAction(.createAttachmentThumbnail(let id, let localUrl)):
|
case .fileAction(.createAttachmentThumbnail(let id, let localName)):
|
||||||
return Future { [weak self] promise in
|
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)
|
self?.downloadingMessageIDs.remove(id)
|
||||||
promise(.success(.fileAction(.attachmentThumbnailCreated(id: id, thumbnailUrl: thumbnailUrl))))
|
promise(.success(.fileAction(.attachmentThumbnailCreated(messageId: id, thumbnailName: thumbnailName))))
|
||||||
} else {
|
} else {
|
||||||
self?.downloadingMessageIDs.remove(id)
|
self?.downloadingMessageIDs.remove(id)
|
||||||
promise(.success(.empty))
|
promise(.success(.empty))
|
||||||
|
|
|
@ -42,9 +42,9 @@ struct Message: DBStorable, Equatable {
|
||||||
let sentError: Bool
|
let sentError: Bool
|
||||||
|
|
||||||
var attachmentType: MessageAttachmentType?
|
var attachmentType: MessageAttachmentType?
|
||||||
var attachmentLocalPath: URL?
|
var attachmentLocalName: String?
|
||||||
var attachmentRemotePath: URL?
|
var attachmentRemotePath: URL?
|
||||||
var attachmentThumbnailPath: URL?
|
var attachmentThumbnailName: String?
|
||||||
var attachmentDownloadFailed: Bool = false
|
var attachmentDownloadFailed: Bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +96,9 @@ extension Message {
|
||||||
pending: false,
|
pending: false,
|
||||||
sentError: false,
|
sentError: false,
|
||||||
attachmentType: nil,
|
attachmentType: nil,
|
||||||
attachmentLocalPath: nil,
|
attachmentLocalName: nil,
|
||||||
attachmentRemotePath: nil,
|
attachmentRemotePath: nil,
|
||||||
attachmentThumbnailPath: nil,
|
attachmentThumbnailName: nil,
|
||||||
attachmentDownloadFailed: false
|
attachmentDownloadFailed: false
|
||||||
)
|
)
|
||||||
if let oob = martinMessage.oob {
|
if let oob = martinMessage.oob {
|
||||||
|
@ -108,3 +108,15 @@ extension Message {
|
||||||
return msg
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -88,8 +88,6 @@ private struct AttachmentView: View {
|
||||||
if let file = message.attachmentLocalPath {
|
if let file = message.attachmentLocalPath {
|
||||||
VideoPlayerView(url: file)
|
VideoPlayerView(url: file)
|
||||||
.frame(width: Const.attachmentPreviewSize, height: Const.attachmentPreviewSize)
|
.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 {
|
} else {
|
||||||
placeholder
|
placeholder
|
||||||
}
|
}
|
||||||
|
@ -135,7 +133,7 @@ private struct AttachmentView: View {
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if let url = message.attachmentRemotePath {
|
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 {
|
private struct VideoPlayerView: UIViewControllerRepresentable {
|
||||||
let url: URL
|
let url: URL
|
||||||
|
|
||||||
func makeUIViewController(context _: Context) -> AVPlayerViewController {
|
func makeUIViewController(context _: Context) -> AVPlayerViewController {
|
||||||
let controller = AVPlayerViewController()
|
let controller = AVPlayerViewController()
|
||||||
controller.player = AVPlayer(url: url)
|
controller.player = AVPlayer(url: url)
|
||||||
|
controller.allowsPictureInPicturePlayback = true
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue