@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.
Files changed (37) hide show
  1. package/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt +1 -2
  2. package/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt +4 -3
  3. package/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt +2 -2
  4. package/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt +26 -28
  5. package/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt +69 -45
  6. package/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt +43 -23
  7. package/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt +83 -0
  8. package/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt +5 -5
  9. package/ios/RNDocumentPicker.mm +19 -41
  10. package/ios/swift/DocPicker.swift +46 -28
  11. package/ios/swift/DocSaver.swift +18 -23
  12. package/ios/swift/DocumentMetadataBuilder.swift +1 -1
  13. package/ios/swift/FileOperations.swift +21 -19
  14. package/ios/swift/IsKnownTypeImpl.swift +16 -10
  15. package/ios/swift/LocalCopyResponse.swift +3 -5
  16. package/ios/swift/PickerBase.swift +31 -49
  17. package/ios/swift/PickerOptions.swift +53 -23
  18. package/ios/swift/PromiseWrapper.swift +71 -58
  19. package/ios/swift/SaverOptions.swift +39 -17
  20. package/jest/build/src/errors.js +2 -0
  21. package/lib/module/errors.js +3 -2
  22. package/lib/module/errors.js.map +1 -1
  23. package/lib/module/release.js +1 -1
  24. package/lib/typescript/src/errors.d.ts +6 -3
  25. package/lib/typescript/src/errors.d.ts.map +1 -1
  26. package/lib/typescript/src/release.d.ts +1 -1
  27. package/lib/typescript/src/saveDocuments.d.ts +1 -1
  28. package/lib/typescript/src/types.d.ts +3 -3
  29. package/package.json +3 -2
  30. package/react-native-document-picker.podspec +1 -0
  31. package/src/errors.ts +8 -4
  32. package/src/release.ts +1 -1
  33. package/src/saveDocuments.ts +1 -1
  34. package/src/types.ts +3 -3
  35. package/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt +0 -36
  36. package/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java +0 -105
  37. 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: PickerWithMetadataImpl {
6
+ @objc public class DocPicker: PickerBase {
7
+ var pickerOptions: PickerOptions?
8
+ var openedUrls: Set<URL> = []
8
9
 
9
- var currentOptions: PickerOptions? = nil
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
- @objc public func present(options: PickerOptions, resolve: @escaping RNDPPromiseResolveBlock, reject: @escaping RNDPPromiseRejectBlock) {
12
- // TODO fix callsite param
13
- if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "pick")) {
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
- documentPicker.modalPresentationStyle = options.presentationStyle
21
- documentPicker.allowsMultipleSelection = options.allowMultiSelection
22
- documentPicker.modalTransitionStyle = options.transitionStyle
23
- // documentPicker.directoryURL = options.initialDirectoryUrl
24
- // documentPicker.shouldShowFileExtensions = options.shouldShowFileExtensions
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
- self.presentInternal(documentPicker: documentPicker)
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
- public func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
31
- return if (currentOptions?.isOpenMode() == true) {
32
- try self.getOpenedDocumentInfo(url: url, requestLongTermAccess: currentOptions?.requestLongTermAccess ?? false)
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
- openedUrls.append(url)
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 URLs content.
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: DocumentMetadataBuilder = DocumentMetadataBuilder(forUri: url)
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 (requestLongTermAccess == true) {
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 == false {
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
  }
@@ -9,33 +9,28 @@
9
9
 
10
10
  import Foundation
11
11
  import UniformTypeIdentifiers
12
- import MobileCoreServices
13
12
 
14
- @objc public class DocSaver: PickerWithMetadataImpl {
13
+ @objc public class DocSaver: PickerBase {
15
14
 
16
- @objc public func present(options: SaverOptions, resolve: @escaping (Any?) -> Void, reject: @escaping (String?, String?, Error?) -> Void) {
17
- if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "saveDocuments")) {
18
- return;
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 getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
33
- let name = url.lastPathComponent.removingPercentEncoding
34
-
35
- var resourceValues = URLResourceValues()
36
- resourceValues.name = name
37
-
38
- return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)
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 (resourceValues?.isDirectory ?? false == false) {
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 RNDPPromiseResolveBlock) {
8
- DispatchQueue.global(qos: .utility).async {
9
- let results = moveFiles(from: from, destinationPreset: destinationPreset)
10
- resolve(results)
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) -> NSDictionary {
15
- let dict = getTypeResult(kind, value: value)
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) -> Dictionary<String, Any?> {
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 ["isKnown": true,
22
- "UTType": utType.identifier,
23
- "preferredFilenameExtension": utType.preferredFilenameExtension,
24
- "mimeType": utType.preferredMIMEType]
20
+ return [
21
+ "isKnown": true,
22
+ "UTType": utType.identifier,
23
+ "preferredFilenameExtension": utType.preferredFilenameExtension ?? NSNull(),
24
+ "mimeType": utType.preferredMIMEType ?? NSNull()
25
+ ]
25
26
  }
26
- return ["isKnown": false, "UTType": nil, "preferredFilenameExtension": nil, "mimeType": nil]
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
- return ["sourceUri": sourceUri, "localUri": localUri, "status": "success"]
20
+ ["sourceUri": sourceUri, "localUri": localUri, "status": "success"]
21
21
  case .error(let sourceUri, let copyError):
22
- var result = ["copyError": copyError, "status": "error"]
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
- // DocSaver.swift
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 MobileCoreServices
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
- func presentInternal(documentPicker: UIDocumentPickerViewController) {
26
- documentPicker.delegate = self
27
- documentPicker.presentationController?.delegate = self;
28
-
29
- if let viewController = RCTPresentedViewController() {
30
- viewController.present(documentPicker, animated: true, completion: nil)
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
- DispatchQueue.global(qos: .userInitiated).async {
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
- private func createDocumentMetadata(for url: URL) -> DocumentMetadataBuilder? {
48
- guard let subclassThatGetsMetadata = self as? GetsMetadataProtocol else {
49
- let error = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil)
50
- self.promiseWrapper.reject("PickerBase", withCode: "BAD_CLASS", withError: error)
51
- return nil
52
- }
53
-
54
- do {
55
- return try subclassThatGetsMetadata.getMetadataFor(url: url)
56
- } catch {
57
- return DocumentMetadataBuilder(forUri: url, error: error)
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
- @objc public class PickerOptions: NSObject {
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: String // "import" or "open"
15
+ let mode: PickerMode
10
16
  let allowMultiSelection: Bool
11
- let transitionStyle: UIModalTransitionStyle
12
- let presentationStyle: UIModalPresentationStyle
13
- let initialDirectoryUrl: URL?
17
+ var transitionStyle: UIModalTransitionStyle?
18
+ var presentationStyle: UIModalPresentationStyle?
19
+ var initialDirectoryUri: URL?
14
20
  let shouldShowFileExtensions: Bool
15
21
  let requestLongTermAccess: Bool
16
-
17
- @objc public init(types: Array<String>, mode: String = "import", initialDirectoryUrl: String? = nil, allowMultiSelection: Bool, shouldShowFileExtensions: Bool, transitionStyle: UIModalTransitionStyle = .coverVertical, presentationStyle: UIModalPresentationStyle = .fullScreen, requestLongTermAccess: Bool = false) {
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
- UTType($0)
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
- public func modeAsCopy() -> Bool {
37
- return self.mode == "import"
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
- public func isOpenMode() -> Bool {
41
- return self.mode == "open"
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
  }