first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
// UIDocumentPickerViewController is unavailable on tvOS
#if os(iOS)
import ExpoModulesCore
import MobileCoreServices
import UIKit
import UniformTypeIdentifiers
internal protocol FilePickingResultHandler {
func didPickFileAt(url: URL)
func didPickDirectoryAt(url: URL)
func didCancelPicking()
}
internal struct FilePickingContext {
let promise: Promise
let initialUri: URL?
let mimeType: String?
let isDirectory: Bool
let delegate: FilePickingDelegate
var pickedUrl: URL?
}
internal class FilePickingDelegate: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate {
private let resultHandler: FilePickingResultHandler
private let isDirectory: Bool
private weak var pickingHandler: FilePickingHandler?
init(resultHandler: FilePickingResultHandler, isDirectory: Bool = false) {
self.resultHandler = resultHandler
self.isDirectory = isDirectory
self.pickingHandler = resultHandler as? FilePickingHandler
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
self.resultHandler.didCancelPicking()
return
}
if isDirectory {
// For directory access, we need to start accessing the security-scoped resource
let didStartAccessing = url.startAccessingSecurityScopedResource()
if didStartAccessing {
// Store the picked URL for proper cleanup
if let pickingHandler = pickingHandler {
pickingHandler.filePickingContext?.pickedUrl = url
}
self.resultHandler.didPickDirectoryAt(url: url)
} else {
// If we can't access the directory, treat as cancellation
self.resultHandler.didCancelPicking()
}
} else {
self.resultHandler.didPickFileAt(url: url)
}
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
self.resultHandler.didCancelPicking()
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.resultHandler.didCancelPicking()
}
}
internal func createFilePicker(initialUri: URL?, mimeType: String?) -> UIDocumentPickerViewController {
if #available(iOS 14.0, *) {
let utTypes: [UTType]
if let mimeType = mimeType {
if let utType = UTType(mimeType: mimeType) {
utTypes = [utType]
} else {
utTypes = [UTType.item]
}
} else {
utTypes = [UTType.item]
}
let picker = UIDocumentPickerViewController(forOpeningContentTypes: utTypes, asCopy: true)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
let utiTypes: [String]
if let mimeType = mimeType {
utiTypes = [toUTI(mimeType: mimeType)]
} else {
utiTypes = [kUTTypeItem as String]
}
let picker = UIDocumentPickerViewController(documentTypes: utiTypes, in: .import)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
internal func createDirectoryPicker(initialUri: URL?) -> UIDocumentPickerViewController {
if #available(iOS 14.0, *) {
// Use UTType.folder for directory access as per Apple's documentation
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.folder], asCopy: false)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
// For iOS 13 and earlier, use kUTTypeFolder
let picker = UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open)
if let initialUri = initialUri {
picker.directoryURL = initialUri
}
return picker
}
@available(iOS 14.0, *)
private func toUTType(mimeType: String) -> UTType? {
switch mimeType {
case "*/*":
return UTType.item
case "image/*":
return UTType.image
case "video/*":
return UTType.movie
case "audio/*":
return UTType.audio
case "text/*":
return UTType.text
default:
return UTType(mimeType: mimeType)
}
}
private func toUTI(mimeType: String) -> String {
var uti: CFString
switch mimeType {
case "*/*":
uti = kUTTypeItem
case "image/*":
uti = kUTTypeImage
case "video/*":
uti = kUTTypeVideo
case "audio/*":
uti = kUTTypeAudio
case "text/*":
uti = kUTTypeText
default:
if let ref = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassMIMEType,
mimeType as CFString,
nil
)?.takeRetainedValue() {
uti = ref
} else {
uti = kUTTypeItem
}
}
return uti as String
}
#endif