@react-native-documents/picker 11.0.4 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt +1 -2
- package/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt +4 -3
- package/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt +2 -2
- package/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt +26 -28
- package/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt +69 -45
- package/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt +43 -23
- package/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt +83 -0
- package/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt +5 -5
- package/ios/RNDocumentPicker.mm +19 -41
- package/ios/swift/DocPicker.swift +46 -28
- package/ios/swift/DocSaver.swift +18 -23
- package/ios/swift/DocumentMetadataBuilder.swift +1 -1
- package/ios/swift/FileOperations.swift +21 -19
- package/ios/swift/IsKnownTypeImpl.swift +16 -10
- package/ios/swift/LocalCopyResponse.swift +3 -5
- package/ios/swift/PickerBase.swift +31 -49
- package/ios/swift/PickerOptions.swift +53 -23
- package/ios/swift/PromiseWrapper.swift +71 -58
- package/ios/swift/SaverOptions.swift +39 -17
- package/jest/build/src/errors.js +2 -0
- package/lib/module/errors.js +3 -2
- package/lib/module/errors.js.map +1 -1
- package/lib/module/release.js +1 -1
- package/lib/typescript/src/errors.d.ts +6 -3
- package/lib/typescript/src/errors.d.ts.map +1 -1
- package/lib/typescript/src/release.d.ts +1 -1
- package/lib/typescript/src/saveDocuments.d.ts +1 -1
- package/lib/typescript/src/types.d.ts +3 -3
- package/package.json +3 -2
- package/react-native-document-picker.podspec +1 -0
- package/src/errors.ts +8 -4
- package/src/release.ts +1 -1
- package/src/saveDocuments.ts +1 -1
- package/src/types.ts +3 -3
- package/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt +0 -36
- package/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java +0 -105
- package/ios/swift/PromiseSupport.swift +0 -2
|
@@ -2,40 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import UniformTypeIdentifiers
|
|
5
|
-
import MobileCoreServices
|
|
6
5
|
|
|
7
|
-
@objc public class DocPicker:
|
|
6
|
+
@objc public class DocPicker: PickerBase {
|
|
7
|
+
var pickerOptions: PickerOptions?
|
|
8
|
+
var openedUrls: Set<URL> = []
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
@MainActor
|
|
11
|
+
override func createDocumentPicker(from dictionary: NSDictionary) -> UIDocumentPickerViewController {
|
|
12
|
+
let options = PickerOptions(dictionary: dictionary)
|
|
13
|
+
self.pickerOptions = options
|
|
14
|
+
return options.createDocumentPicker()
|
|
15
|
+
}
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
currentOptions = options;
|
|
17
|
-
DispatchQueue.main.async {
|
|
18
|
-
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: options.allowedTypes, asCopy: options.modeAsCopy())
|
|
17
|
+
public override func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
|
18
|
+
guard let promise = promiseWrapper.takeCallbacks() else { return }
|
|
19
|
+
let options = self.pickerOptions
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Task.detached(priority: .userInitiated) {
|
|
22
|
+
let documentsInfo = self.createDocumentMetadataWithOptions(for: urls, options: options)
|
|
23
|
+
.compactMap { $0.build() }
|
|
24
|
+
promise.resolve(documentsInfo)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
nonisolated private func createDocumentMetadataWithOptions(for urls: [URL], options: PickerOptions?) -> [DocumentMetadataBuilder] {
|
|
29
|
+
return urls.compactMap { url in
|
|
30
|
+
do {
|
|
31
|
+
return try self.getMetadataForWithOptions(url: url, options: options)
|
|
32
|
+
} catch {
|
|
33
|
+
return DocumentMetadataBuilder(forUri: url, error: error)
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
return if
|
|
32
|
-
try self.getOpenedDocumentInfo(url: url, requestLongTermAccess:
|
|
38
|
+
nonisolated private func getMetadataForWithOptions(url: URL, options: PickerOptions?) throws -> DocumentMetadataBuilder {
|
|
39
|
+
return if options?.isOpenMode() == true {
|
|
40
|
+
try self.getOpenedDocumentInfo(url: url, requestLongTermAccess: options?.requestLongTermAccess ?? false)
|
|
33
41
|
} else {
|
|
34
42
|
try self.getAnyModeMetadata(url: url)
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
private func getAnyModeMetadata(url: URL) throws -> DocumentMetadataBuilder {
|
|
46
|
+
nonisolated private func getAnyModeMetadata(url: URL) throws -> DocumentMetadataBuilder {
|
|
39
47
|
let resourceValues = try url.resourceValues(forKeys: [.fileSizeKey, .nameKey, .isDirectoryKey, .contentTypeKey])
|
|
40
48
|
|
|
41
49
|
return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)
|
|
@@ -45,18 +53,20 @@ import MobileCoreServices
|
|
|
45
53
|
case sourceAccessError
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
func getOpenedDocumentInfo(url: URL, requestLongTermAccess: Bool) throws -> DocumentMetadataBuilder {
|
|
56
|
+
nonisolated private func getOpenedDocumentInfo(url: URL, requestLongTermAccess: Bool) throws -> DocumentMetadataBuilder {
|
|
49
57
|
guard url.startAccessingSecurityScopedResource() else {
|
|
50
58
|
throw KeepLocalCopyError.sourceAccessError
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
// url.stopAccessingSecurityScopedResource() must be called later
|
|
54
|
-
|
|
61
|
+
// url.stopAccessingSecurityScopedResource() must be called later by user
|
|
62
|
+
DispatchQueue.main.async { [weak self] in
|
|
63
|
+
self?.openedUrls.insert(url)
|
|
64
|
+
}
|
|
55
65
|
|
|
56
|
-
// Use file coordination for reading and writing any of the URL
|
|
66
|
+
// Use file coordination for reading and writing any of the URL's content.
|
|
57
67
|
var error: NSError? = nil
|
|
58
68
|
var success = false
|
|
59
|
-
var metadataBuilder
|
|
69
|
+
var metadataBuilder = DocumentMetadataBuilder(forUri: url)
|
|
60
70
|
|
|
61
71
|
NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in
|
|
62
72
|
do {
|
|
@@ -66,7 +76,7 @@ import MobileCoreServices
|
|
|
66
76
|
metadataBuilder.setMetadataReadingError(error)
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
if
|
|
79
|
+
if requestLongTermAccess {
|
|
70
80
|
do {
|
|
71
81
|
let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
|
|
72
82
|
metadataBuilder.setBookmark(bookmarkData)
|
|
@@ -75,10 +85,18 @@ import MobileCoreServices
|
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
|
-
if let err = error, success
|
|
88
|
+
if let err = error, !success {
|
|
79
89
|
throw err
|
|
80
90
|
}
|
|
81
91
|
return metadataBuilder
|
|
82
92
|
}
|
|
83
93
|
|
|
94
|
+
@objc public func stopAccessingOpenedUrls(_ urlStrings: [String]) {
|
|
95
|
+
let incomingUrls = Set(urlStrings.compactMap { URL(string: $0) })
|
|
96
|
+
for url in openedUrls.intersection(incomingUrls) {
|
|
97
|
+
url.stopAccessingSecurityScopedResource()
|
|
98
|
+
openedUrls.remove(url)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
84
102
|
}
|
package/ios/swift/DocSaver.swift
CHANGED
|
@@ -9,33 +9,28 @@
|
|
|
9
9
|
|
|
10
10
|
import Foundation
|
|
11
11
|
import UniformTypeIdentifiers
|
|
12
|
-
import MobileCoreServices
|
|
13
12
|
|
|
14
|
-
@objc public class DocSaver:
|
|
13
|
+
@objc public class DocSaver: PickerBase {
|
|
15
14
|
|
|
16
|
-
@
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
DispatchQueue.main.async {
|
|
21
|
-
let documentPicker = UIDocumentPickerViewController(forExporting: options.sourceUrls, asCopy: options.asCopy)
|
|
22
|
-
|
|
23
|
-
documentPicker.modalPresentationStyle = options.presentationStyle
|
|
24
|
-
documentPicker.modalTransitionStyle = options.transitionStyle
|
|
25
|
-
// documentPicker.directoryURL = options.initialDirectoryUrl
|
|
26
|
-
// documentPicker.shouldShowFileExtensions = options.shouldShowFileExtensions
|
|
27
|
-
|
|
28
|
-
self.presentInternal(documentPicker: documentPicker)
|
|
29
|
-
}
|
|
15
|
+
@MainActor
|
|
16
|
+
override func createDocumentPicker(from dictionary: NSDictionary) -> UIDocumentPickerViewController {
|
|
17
|
+
let options = SaverOptions(dictionary: dictionary)
|
|
18
|
+
return options.createDocumentPicker()
|
|
30
19
|
}
|
|
31
20
|
|
|
32
|
-
public func
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
public override func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
|
22
|
+
guard let promise = promiseWrapper.takeCallbacks() else { return }
|
|
23
|
+
|
|
24
|
+
Task.detached(priority: .userInitiated) {
|
|
25
|
+
// runs off main thread - preserves I/O performance
|
|
26
|
+
let documentsInfo = urls.compactMap { url -> [String: Any?]? in
|
|
27
|
+
let name = url.lastPathComponent.removingPercentEncoding
|
|
28
|
+
var resourceValues = URLResourceValues()
|
|
29
|
+
resourceValues.name = name
|
|
30
|
+
return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues).build()
|
|
31
|
+
}
|
|
32
|
+
promise.resolve(documentsInfo)
|
|
33
|
+
}
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
}
|
|
@@ -39,7 +39,7 @@ public class DocumentMetadataBuilder {
|
|
|
39
39
|
|
|
40
40
|
func build() -> [String: Any?] {
|
|
41
41
|
var dictionary: [String: Any?] = [:]
|
|
42
|
-
if
|
|
42
|
+
if resourceValues?.isDirectory != true {
|
|
43
43
|
let utTypeFromFile: UTType? = resourceValues?.contentType
|
|
44
44
|
let utType: UTType? = utTypeFromFile ?? UTType(filenameExtension: uri.pathExtension)
|
|
45
45
|
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
// LICENSE: see License.md in the package root
|
|
2
2
|
|
|
3
3
|
import Foundation
|
|
4
|
+
import React
|
|
4
5
|
|
|
5
6
|
@objc public class FileOperations: NSObject {
|
|
6
|
-
|
|
7
|
-
@objc public static func keepLocalCopyAtUniqueDestination(from: [[String: String]], destinationPreset: String, resolve: @escaping
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
|
|
8
|
+
@objc public static func keepLocalCopyAtUniqueDestination(from: [[String: String]], destinationPreset: String, resolve: @escaping RCTPromiseResolveBlock) {
|
|
9
|
+
let callback = ResolveCallback(resolve: resolve)
|
|
10
|
+
Task.detached(priority: .userInitiated) {
|
|
11
|
+
let results = Self.moveFiles(from: from, destinationPreset: destinationPreset)
|
|
12
|
+
callback.resolve(results)
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
static func moveFiles(from: [[String: String]], destinationPreset: String) -> [[String: String?]] {
|
|
15
|
+
|
|
16
|
+
private static func moveFiles(from: [[String: String]], destinationPreset: String) -> [[String: String?]] {
|
|
15
17
|
let destinationRootDir = getDirectoryForFileDestination(destinationPreset)
|
|
16
18
|
let uniqueSubDirName = UUID().uuidString
|
|
17
19
|
let destinationDir = destinationRootDir.appendingPathComponent(uniqueSubDirName, isDirectory: true)
|
|
18
|
-
|
|
20
|
+
|
|
19
21
|
do {
|
|
20
22
|
try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true, attributes: nil)
|
|
21
23
|
} catch {
|
|
@@ -23,13 +25,13 @@ import Foundation
|
|
|
23
25
|
LocalCopyResponse.error(sourceUri: dictionary["uri"], copyError: "Failed to create destination directory: \(error.localizedDescription)").dictionaryRepresentation
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
// move files
|
|
28
30
|
return from.map { dictionary in
|
|
29
|
-
moveSingleFile(dictionary: dictionary, destinationDir: destinationDir).dictionaryRepresentation
|
|
31
|
+
Self.moveSingleFile(dictionary: dictionary, destinationDir: destinationDir).dictionaryRepresentation
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
|
|
33
35
|
private static func moveSingleFile(dictionary: [String: String], destinationDir: URL) -> LocalCopyResponse {
|
|
34
36
|
guard let uriString = dictionary["uri"],
|
|
35
37
|
let uri = URL(string: uriString),
|
|
@@ -39,7 +41,7 @@ import Foundation
|
|
|
39
41
|
copyError: "Invalid URI or fileName"
|
|
40
42
|
)
|
|
41
43
|
}
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
do {
|
|
44
46
|
let destinationUrl = try moveToDestination(from: uri, usingFilename: fileName, destinationDir: destinationDir)
|
|
45
47
|
return LocalCopyResponse.success(sourceUri: uri.absoluteString, localUri: destinationUrl.absoluteString)
|
|
@@ -47,10 +49,10 @@ import Foundation
|
|
|
47
49
|
return LocalCopyResponse.error(sourceUri: uriString, copyError: error.localizedDescription)
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
static func moveToDestination(from: URL, usingFilename fileName: String, destinationDir: URL) throws -> URL {
|
|
52
|
+
|
|
53
|
+
private static func moveToDestination(from: URL, usingFilename fileName: String, destinationDir: URL) throws -> URL {
|
|
52
54
|
let destinationFile = destinationDir.appendingPathComponent(fileName).standardized
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
guard destinationFile.path.hasPrefix(destinationDir.standardized.path) else {
|
|
55
57
|
throw NSError(
|
|
56
58
|
domain: "PathTraversalPrevention",
|
|
@@ -58,13 +60,13 @@ import Foundation
|
|
|
58
60
|
userInfo: [NSLocalizedDescriptionKey: "The copied file is attempting to write outside of the target directory."]
|
|
59
61
|
)
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
try FileManager.default.moveItem(at: from, to: destinationFile)
|
|
63
|
-
|
|
65
|
+
|
|
64
66
|
return destinationFile
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
static func getDirectoryForFileDestination(_ copyToDirectory: String) -> URL {
|
|
68
|
+
|
|
69
|
+
private static func getDirectoryForFileDestination(_ copyToDirectory: String) -> URL {
|
|
68
70
|
let searchPath: FileManager.SearchPathDirectory = copyToDirectory == "documentDirectory" ? .documentDirectory : .cachesDirectory
|
|
69
71
|
return FileManager.default.urls(for: searchPath, in: .userDomainMask).first!
|
|
70
72
|
}
|
|
@@ -10,20 +10,26 @@ import Foundation
|
|
|
10
10
|
import UniformTypeIdentifiers
|
|
11
11
|
|
|
12
12
|
@objc public class IsKnownTypeImpl: NSObject {
|
|
13
|
-
|
|
14
|
-
@objc public static func checkType(_ kind: String, value: String) ->
|
|
15
|
-
|
|
16
|
-
return NSDictionary(dictionary: dict as [AnyHashable: Any])
|
|
13
|
+
|
|
14
|
+
@objc public static func checkType(_ kind: String, value: String) -> [String: Any] {
|
|
15
|
+
return getTypeResult(kind, value: value)
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
static func getTypeResult(_ kind: String, value: String) ->
|
|
18
|
+
static func getTypeResult(_ kind: String, value: String) -> [String: Any] {
|
|
20
19
|
if let utType = createUTType(kind: kind, value: value), utType.isDeclared == true {
|
|
21
|
-
return [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
return [
|
|
21
|
+
"isKnown": true,
|
|
22
|
+
"UTType": utType.identifier,
|
|
23
|
+
"preferredFilenameExtension": utType.preferredFilenameExtension ?? NSNull(),
|
|
24
|
+
"mimeType": utType.preferredMIMEType ?? NSNull()
|
|
25
|
+
]
|
|
25
26
|
}
|
|
26
|
-
return [
|
|
27
|
+
return [
|
|
28
|
+
"isKnown": false,
|
|
29
|
+
"UTType": NSNull(),
|
|
30
|
+
"preferredFilenameExtension": NSNull(),
|
|
31
|
+
"mimeType": NSNull()
|
|
32
|
+
]
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
static func createUTType(kind: String, value: String) -> UTType? {
|
|
@@ -15,13 +15,11 @@ enum LocalCopyResponse {
|
|
|
15
15
|
case error(sourceUri: String?, copyError: String)
|
|
16
16
|
|
|
17
17
|
var dictionaryRepresentation: [String: String?] {
|
|
18
|
-
switch self {
|
|
18
|
+
return switch self {
|
|
19
19
|
case .success(let sourceUri, let localUri):
|
|
20
|
-
|
|
20
|
+
["sourceUri": sourceUri, "localUri": localUri, "status": "success"]
|
|
21
21
|
case .error(let sourceUri, let copyError):
|
|
22
|
-
|
|
23
|
-
result["sourceUri"] = sourceUri ?? nil
|
|
24
|
-
return result
|
|
22
|
+
["sourceUri": sourceUri, "copyError": copyError, "status": "error"]
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//
|
|
2
|
-
//
|
|
2
|
+
// PickerBase.swift
|
|
3
3
|
// react-native-document-picker
|
|
4
4
|
//
|
|
5
5
|
// Created by Vojtech Novak on 25.05.2024.
|
|
@@ -9,55 +9,46 @@
|
|
|
9
9
|
|
|
10
10
|
import Foundation
|
|
11
11
|
import UniformTypeIdentifiers
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
public protocol GetsMetadataProtocol {
|
|
15
|
-
func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// https://stackoverflow.com/a/51333906/2070942
|
|
19
|
-
public typealias PickerWithMetadataImpl = PickerBase & GetsMetadataProtocol
|
|
12
|
+
import React
|
|
20
13
|
|
|
21
14
|
public class PickerBase: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate {
|
|
22
15
|
let promiseWrapper = PromiseWrapper()
|
|
23
|
-
var openedUrls: Array<URL> = []
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} else {
|
|
32
|
-
let error = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil)
|
|
33
|
-
promiseWrapper.reject("RCTPresentedViewController was nil", withCode: "PRESENTER_IS_NULL", withError: error)
|
|
17
|
+
@MainActor
|
|
18
|
+
@objc public func present(optionsDict: NSDictionary,
|
|
19
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
20
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
21
|
+
guard promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject) else {
|
|
22
|
+
return
|
|
34
23
|
}
|
|
24
|
+
|
|
25
|
+
presentInternal(optionsDict)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@MainActor
|
|
29
|
+
func createDocumentPicker(from dictionary: NSDictionary) -> UIDocumentPickerViewController {
|
|
30
|
+
fatalError("Subclasses must override createDocumentPicker(from:)")
|
|
35
31
|
}
|
|
36
|
-
|
|
32
|
+
|
|
33
|
+
// Subclasses must override this method to process picked documents
|
|
37
34
|
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
|
38
|
-
|
|
39
|
-
// this doesn't run on the main thread
|
|
40
|
-
let documentsInfo = urls.compactMap(self.createDocumentMetadata).compactMap { $0.build() }
|
|
41
|
-
self.promiseWrapper.resolve(documentsInfo)
|
|
42
|
-
}
|
|
43
|
-
// https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/DocumentPickerProgrammingGuide/AccessingDocuments/AccessingDocuments.html#//apple_ref/doc/uid/TP40014451-CH2-SW4 "Accessing Files Outside Your Sandbox"
|
|
44
|
-
// https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
|
|
35
|
+
fatalError("Subclasses must override documentPicker(_:didPickDocumentsAt:)")
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
37
|
+
|
|
38
|
+
@MainActor
|
|
39
|
+
func presentInternal(_ optionsDict: NSDictionary) {
|
|
40
|
+
let documentPicker = self.createDocumentPicker(from: optionsDict)
|
|
41
|
+
|
|
42
|
+
documentPicker.delegate = self
|
|
43
|
+
documentPicker.presentationController?.delegate = self
|
|
44
|
+
|
|
45
|
+
if let viewController = RCTPresentedViewController() {
|
|
46
|
+
viewController.present(documentPicker, animated: true, completion: nil)
|
|
47
|
+
} else {
|
|
48
|
+
promiseWrapper.takeCallbacks()?.reject("RCTPresentedViewController was nil", withCode: "NULL_PRESENTER", withError: nil)
|
|
58
49
|
}
|
|
59
50
|
}
|
|
60
|
-
|
|
51
|
+
|
|
61
52
|
public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
|
62
53
|
promiseWrapper.rejectAsUserCancelledOperation()
|
|
63
54
|
}
|
|
@@ -66,13 +57,4 @@ public class PickerBase: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentat
|
|
|
66
57
|
promiseWrapper.rejectAsUserCancelledOperation()
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
@objc public func stopAccessingOpenedUrls(_ urlStrings: [String]) {
|
|
70
|
-
let incomingUrls = Set(urlStrings.compactMap { URL(string: $0) })
|
|
71
|
-
openedUrls.removeAll { url in
|
|
72
|
-
guard incomingUrls.contains(url) else { return false }
|
|
73
|
-
url.stopAccessingSecurityScopedResource()
|
|
74
|
-
return true
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
60
|
}
|
|
@@ -3,42 +3,72 @@
|
|
|
3
3
|
import Foundation
|
|
4
4
|
import UIKit
|
|
5
5
|
import UniformTypeIdentifiers
|
|
6
|
+
import React
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
public enum PickerMode: String, Sendable {
|
|
9
|
+
case `import` = "import"
|
|
10
|
+
case open = "open"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public struct PickerOptions: Sendable {
|
|
8
14
|
let allowedTypes: Array<UTType>
|
|
9
|
-
let mode:
|
|
15
|
+
let mode: PickerMode
|
|
10
16
|
let allowMultiSelection: Bool
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
var transitionStyle: UIModalTransitionStyle?
|
|
18
|
+
var presentationStyle: UIModalPresentationStyle?
|
|
19
|
+
var initialDirectoryUri: URL?
|
|
14
20
|
let shouldShowFileExtensions: Bool
|
|
15
21
|
let requestLongTermAccess: Bool
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
|
|
23
|
+
public init(dictionary: NSDictionary) {
|
|
24
|
+
let types = dictionary["type"] as? [String] ?? []
|
|
25
|
+
let modeString = dictionary["mode"] as? String ?? "import"
|
|
26
|
+
let allowMultiSelection = dictionary["allowMultiSelection"] as? Bool ?? false
|
|
27
|
+
let shouldShowFileExtensions = dictionary["showFileExtensions"] as? Bool ?? false
|
|
28
|
+
let requestLongTermAccess = dictionary["requestLongTermAccess"] as? Bool ?? false
|
|
29
|
+
|
|
18
30
|
// TODO check if types were valid
|
|
19
|
-
allowedTypes = types.compactMap {
|
|
20
|
-
|
|
21
|
-
}
|
|
31
|
+
self.allowedTypes = types.compactMap { UTType($0) }
|
|
32
|
+
self.mode = PickerMode(rawValue: modeString) ?? .import
|
|
22
33
|
self.allowMultiSelection = allowMultiSelection
|
|
23
|
-
self.transitionStyle = transitionStyle
|
|
24
|
-
self.presentationStyle = presentationStyle
|
|
25
|
-
self.mode = mode
|
|
26
|
-
if let unwrappedUrl = initialDirectoryUrl, let url = URL(string: unwrappedUrl) {
|
|
27
|
-
self.initialDirectoryUrl = url
|
|
28
|
-
} else {
|
|
29
|
-
self.initialDirectoryUrl = nil
|
|
30
|
-
}
|
|
31
34
|
self.shouldShowFileExtensions = shouldShowFileExtensions
|
|
32
35
|
self.requestLongTermAccess = requestLongTermAccess
|
|
36
|
+
|
|
37
|
+
if let transitionStyle = dictionary["transitionStyle"] as? String {
|
|
38
|
+
self.transitionStyle = RCTConvert.uiModalTransitionStyle(transitionStyle)
|
|
39
|
+
}
|
|
40
|
+
if let presentationStyle = dictionary["presentationStyle"] as? String {
|
|
41
|
+
self.presentationStyle = RCTConvert.uiModalPresentationStyle(presentationStyle)
|
|
42
|
+
}
|
|
43
|
+
if let initialDirectoryUri = dictionary["initialDirectoryUri"] as? String, let url = URL(string: initialDirectoryUri) {
|
|
44
|
+
self.initialDirectoryUri = url
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
|
-
|
|
47
|
+
|
|
35
48
|
// asCopy: if true, the picker will give you access to a local copy of the document, otherwise you will have access to the original document
|
|
36
|
-
|
|
37
|
-
return self.mode ==
|
|
49
|
+
func modeAsCopy() -> Bool {
|
|
50
|
+
return self.mode == .import
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func isOpenMode() -> Bool {
|
|
54
|
+
return self.mode == .open
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
@MainActor
|
|
58
|
+
public func createDocumentPicker() -> UIDocumentPickerViewController {
|
|
59
|
+
let picker = UIDocumentPickerViewController(forOpeningContentTypes: allowedTypes, asCopy: modeAsCopy())
|
|
60
|
+
picker.allowsMultipleSelection = allowMultiSelection
|
|
61
|
+
|
|
62
|
+
if let presentationStyle = presentationStyle {
|
|
63
|
+
picker.modalPresentationStyle = presentationStyle
|
|
64
|
+
}
|
|
65
|
+
if let transitionStyle = transitionStyle {
|
|
66
|
+
picker.modalTransitionStyle = transitionStyle
|
|
67
|
+
}
|
|
68
|
+
picker.directoryURL = initialDirectoryUri
|
|
69
|
+
picker.shouldShowFileExtensions = shouldShowFileExtensions
|
|
70
|
+
|
|
71
|
+
return picker
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
}
|