UI support for uploading image attachments
This commit is contained in:
101
kordophone2/Attachments.swift
Normal file
101
kordophone2/Attachments.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Attachments.swift
|
||||
// kordophone2
|
||||
//
|
||||
// Created by Assistant on 8/31/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
enum AttachmentState
|
||||
{
|
||||
case staged
|
||||
case uploading(progress: Double)
|
||||
case uploaded
|
||||
case failed
|
||||
}
|
||||
|
||||
struct OutgoingAttachment: Identifiable, Hashable
|
||||
{
|
||||
let id: String
|
||||
let originalURL: URL
|
||||
let stagedURL: URL
|
||||
let fileName: String
|
||||
let contentType: UTType?
|
||||
var state: AttachmentState
|
||||
|
||||
init(id: String = UUID().uuidString,
|
||||
originalURL: URL,
|
||||
stagedURL: URL,
|
||||
fileName: String? = nil,
|
||||
contentType: UTType? = nil,
|
||||
state: AttachmentState = .staged)
|
||||
{
|
||||
self.id = id
|
||||
self.originalURL = originalURL
|
||||
self.stagedURL = stagedURL
|
||||
self.fileName = fileName ?? originalURL.lastPathComponent
|
||||
self.contentType = contentType
|
||||
self.state = state
|
||||
}
|
||||
|
||||
static func ==(lhs: OutgoingAttachment, rhs: OutgoingAttachment) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
hasher.combine(originalURL)
|
||||
hasher.combine(stagedURL)
|
||||
hasher.combine(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
final class AttachmentManager
|
||||
{
|
||||
static let shared = AttachmentManager()
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
private let stagingRoot: URL
|
||||
|
||||
private init() {
|
||||
let base = fileManager.temporaryDirectory.appendingPathComponent("kordophone-staging", isDirectory: true)
|
||||
try? fileManager.createDirectory(at: base, withIntermediateDirectories: true)
|
||||
self.stagingRoot = base
|
||||
}
|
||||
|
||||
func stageIfImage(url: URL) -> OutgoingAttachment? {
|
||||
guard isImage(url: url) else { return nil }
|
||||
let staged = stage(url: url)
|
||||
let type = (try? url.resourceValues(forKeys: [.contentTypeKey]))?.contentType
|
||||
?? UTType(filenameExtension: url.pathExtension)
|
||||
return OutgoingAttachment(originalURL: url, stagedURL: staged, fileName: url.lastPathComponent, contentType: type)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func isImage(url: URL) -> Bool {
|
||||
if let type = (try? url.resourceValues(forKeys: [.contentTypeKey]))?.contentType {
|
||||
return type.conforms(to: .image)
|
||||
}
|
||||
return UTType(filenameExtension: url.pathExtension)?.conforms(to: .image) ?? false
|
||||
}
|
||||
|
||||
private func stage(url: URL) -> URL {
|
||||
let ext = url.pathExtension
|
||||
let dest = stagingRoot.appendingPathComponent(UUID().uuidString).appendingPathExtension(ext)
|
||||
do {
|
||||
if fileManager.fileExists(atPath: dest.path) { try? fileManager.removeItem(at: dest) }
|
||||
try fileManager.copyItem(at: url, to: dest)
|
||||
return dest
|
||||
} catch {
|
||||
do {
|
||||
try fileManager.moveItem(at: url, to: dest)
|
||||
return dest
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user