Fix drag and drop functionality for audio files

Improvements made:
- Expand drop target to entire view area for better UX
- Add support for multiple UTTypes (.fileURL, .data, .audio, .movie)
- Improve file validation with both extension and UTType checking
- Add security scoped resource handling
- Enhanced error logging for debugging
- Support more audio formats (AAC, FLAC, CAF)

This resolves issues where dragging files from desktop/Finder would not work while manual file selection did.
This commit is contained in:
slumdev88 2025-07-04 21:59:02 +08:00
parent 78dcc0dfa4
commit 6a4ccb0db3

View File

@ -96,6 +96,13 @@ struct AudioTranscribeView: View {
}
}
}
.onDrop(of: [.fileURL, .data, .audio, .movie], isTargeted: $isDropTargeted) { providers in
if !transcriptionManager.isProcessing && !isAudioFileSelected {
handleDroppedFile(providers)
return true
}
return false
}
.alert("Error", isPresented: .constant(transcriptionManager.errorMessage != nil)) {
Button("OK", role: .cancel) {
transcriptionManager.errorMessage = nil
@ -245,12 +252,6 @@ struct AudioTranscribeView: View {
.foregroundColor(.secondary)
}
.padding()
.onDrop(of: [.fileURL], isTargeted: $isDropTargeted) { providers in
Task {
await handleDroppedFile(providers)
}
return true
}
}
private var processingView: some View {
@ -284,18 +285,101 @@ struct AudioTranscribeView: View {
}
}
private func handleDroppedFile(_ providers: [NSItemProvider]) async {
private func handleDroppedFile(_ providers: [NSItemProvider]) {
guard let provider = providers.first else { return }
if let item = try? await provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier),
let url = item as? URL {
Task { @MainActor in
selectedAudioURL = url
isAudioFileSelected = true
// List of type identifiers to try
let typeIdentifiers = [
UTType.fileURL.identifier,
UTType.audio.identifier,
UTType.movie.identifier,
UTType.data.identifier,
"public.file-url"
]
// Try each type identifier
for typeIdentifier in typeIdentifiers {
if provider.hasItemConformingToTypeIdentifier(typeIdentifier) {
provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { (item, error) in
if let error = error {
print("Error loading dropped file with type \(typeIdentifier): \(error)")
return
}
var fileURL: URL?
if let url = item as? URL {
fileURL = url
} else if let data = item as? Data {
// Try to create URL from data
if let url = URL(dataRepresentation: data, relativeTo: nil) {
fileURL = url
} else if let urlString = String(data: data, encoding: .utf8),
let url = URL(string: urlString) {
fileURL = url
}
} else if let urlString = item as? String {
fileURL = URL(string: urlString)
}
if let finalURL = fileURL {
DispatchQueue.main.async {
self.validateAndSetAudioFile(finalURL)
}
return
}
}
break // Stop trying other types once we find a compatible one
}
}
}
private func validateAndSetAudioFile(_ url: URL) {
print("Attempting to validate file: \(url.path)")
// Check if file exists
guard FileManager.default.fileExists(atPath: url.path) else {
print("File does not exist at path: \(url.path)")
return
}
// Try to access security scoped resource
let accessing = url.startAccessingSecurityScopedResource()
defer {
if accessing {
url.stopAccessingSecurityScopedResource()
}
}
// Validate file type by extension
let supportedExtensions = ["wav", "mp3", "m4a", "aiff", "mp4", "mov", "aac", "flac", "caf"]
let fileExtension = url.pathExtension.lowercased()
// Check file extension first
if !fileExtension.isEmpty && supportedExtensions.contains(fileExtension) {
print("File type validated by extension: \(fileExtension)")
} else {
print("Unsupported file extension: \(fileExtension)")
// Try to validate by UTType as well
if let resourceValues = try? url.resourceValues(forKeys: [.contentTypeKey]),
let contentType = resourceValues.contentType {
if contentType.conforms(to: .audio) || contentType.conforms(to: .movie) {
print("File type validated by UTType: \(contentType.identifier)")
} else {
print("File does not conform to audio or movie type: \(contentType.identifier)")
return
}
} else {
print("Could not validate file type")
return
}
}
print("File validated successfully: \(url.lastPathComponent)")
selectedAudioURL = url
isAudioFileSelected = true
}
private func formatDuration(_ duration: TimeInterval) -> String {
let minutes = Int(duration) / 60
let seconds = Int(duration) % 60