Files
2026-06-01 10:53:28 -04:00

142 lines
5.1 KiB
Swift

import SwiftUI
import UniformTypeIdentifiers
public struct DropZoneView: View {
@State private var isDragging = false
@State private var uploadResult: UploadResult?
@State private var error: String?
@State private var isUploading = false
@State private var uploadProgress: Double = 0
public let uploadManager: UploadManager
public init(uploadManager: UploadManager) {
self.uploadManager = uploadManager
}
public var body: some View {
VStack(spacing: 20) {
Image(systemName: "arrow.up.doc.fill")
.font(.system(size: 48))
.foregroundColor(isDragging ? .blue : .secondary)
Text("Drop files here")
.font(.title2)
if isUploading {
ProgressView(value: uploadProgress)
.frame(width: 180)
Text("\(Int(uploadProgress * 100))%")
.font(.caption)
.foregroundColor(.secondary)
} else if let result = uploadResult {
VStack(spacing: 8) {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 32))
.foregroundColor(.green)
Text("Uploaded \(result.filename)")
.font(.headline)
Text("URL copied to clipboard")
.font(.caption)
.foregroundColor(.secondary)
Text(result.url.absoluteString)
.font(.caption2)
.foregroundColor(.blue)
.lineLimit(1)
.truncationMode(.middle)
.padding(.horizontal)
}
} else if let error = error {
VStack(spacing: 8) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 32))
.foregroundColor(.red)
Text("Upload failed")
.font(.headline)
Text(error)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
.frame(width: 300, height: 250)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isDragging ? Color.blue.opacity(0.1) : Color.secondary.opacity(0.1))
.overlay(
RoundedRectangle(cornerRadius: 12)
.strokeBorder(isDragging ? Color.blue : Color.secondary.opacity(0.3), style: StrokeStyle(lineWidth: 2, dash: [8]))
)
)
.onDrop(of: [.fileURL], isTargeted: $isDragging) { providers in
guard let provider = providers.first else { return false }
_ = provider.loadObject(ofClass: URL.self) { url, error in
guard let url = url else { return }
Task {
await MainActor.run {
isUploading = true
uploadProgress = 0
self.error = nil
self.uploadResult = nil
}
do {
let result = try await uploadFileResolvingDuplicates(url, onProgress: { progress in
Task { @MainActor in
uploadProgress = min(max(progress, 0), 1)
}
})
await MainActor.run {
uploadResult = result
isUploading = false
uploadProgress = 0
}
} catch {
await MainActor.run {
self.error = error.localizedDescription
isUploading = false
uploadProgress = 0
}
}
}
}
return true
}
}
private func uploadFileResolvingDuplicates(
_ url: URL,
onProgress: UploadProgressHandler?
) async throws -> UploadResult {
var duplicateAction: UploadDuplicateAction?
while true {
do {
return try await uploadManager.uploadFile(
url,
duplicateAction: duplicateAction,
onProgress: onProgress
)
} catch let error as APIError where error.isUploadConflict {
guard let action = promptForUploadDuplicate(error, filename: url.lastPathComponent) else {
throw UploadCancelledError()
}
duplicateAction = action
}
}
}
}
#Preview {
// This preview won't work without a real UploadManager
Text("DropZoneView Preview")
}