expo-document-picker 11.2.2 → 11.3.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/CHANGELOG.md CHANGED
@@ -10,7 +10,14 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 11.2.2 — 2023-03-28
13
+ ## 11.3.0 — 2023-04-14
14
+
15
+ ### 🎉 New features
16
+
17
+ - Migrated to Expo Modules API. ([#20336](https://github.com/expo/expo/pull/20336) by [@alanhughes](https://github.com/alanjhughes))
18
+ - Added support for picking multiple documents. ([#20365](https://github.com/expo/expo/pull/20365) by [@alanhughes](https://github.com/alanjhughes))
19
+
20
+ ## 11.2.2 - 2023-03-28
14
21
 
15
22
  ### 🐛 Bug fixes
16
23
 
package/README.md CHANGED
@@ -9,7 +9,7 @@ Provides access to the system's UI for selecting documents from the available pr
9
9
 
10
10
  # Installation in managed Expo projects
11
11
 
12
- For [managed](https://docs.expo.dev/versions/latest/introduction/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/document-picker/).
12
+ For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/document-picker/).
13
13
 
14
14
  # Installation in bare React Native projects
15
15
 
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
3
3
  apply plugin: 'maven-publish'
4
4
 
5
5
  group = 'host.exp.exponent'
6
- version = '11.2.2'
6
+ version = '11.3.0'
7
7
 
8
8
  buildscript {
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
@@ -74,7 +74,7 @@ android {
74
74
  minSdkVersion safeExtGet("minSdkVersion", 21)
75
75
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
76
76
  versionCode 17
77
- versionName '11.2.2'
77
+ versionName '11.3.0'
78
78
  }
79
79
  lintOptions {
80
80
  abortOnError false
@@ -6,6 +6,7 @@ import android.content.Intent
6
6
  import android.net.Uri
7
7
  import expo.modules.core.utilities.FileUtilities
8
8
  import expo.modules.kotlin.Promise
9
+ import expo.modules.kotlin.exception.CodedException
9
10
  import expo.modules.kotlin.exception.Exceptions
10
11
  import expo.modules.kotlin.modules.Module
11
12
  import expo.modules.kotlin.modules.ModuleDefinition
@@ -36,6 +37,7 @@ class DocumentPickerModule : Module() {
36
37
  copyToCacheDirectory = options.copyToCacheDirectory
37
38
  val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
38
39
  addCategory(Intent.CATEGORY_OPENABLE)
40
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, options.multiple)
39
41
  type = if (options.type.size > 1) {
40
42
  putExtra(Intent.EXTRA_MIME_TYPES, options.type.toTypedArray())
41
43
  "*/*"
@@ -52,34 +54,24 @@ class DocumentPickerModule : Module() {
52
54
  }
53
55
 
54
56
  val promise = pendingPromise!!
55
- pendingPromise = null
56
57
 
57
58
  if (resultCode == Activity.RESULT_OK) {
58
- intent?.data?.let { uri ->
59
- val originalDocumentDetails = DocumentDetailsReader(context).read(uri)
60
- if (!copyToCacheDirectory || originalDocumentDetails == null) {
61
- originalDocumentDetails
59
+ try {
60
+ if (intent?.clipData != null) {
61
+ handleMultipleSelection(intent)
62
62
  } else {
63
- val copyPath = copyDocumentToCacheDirectory(uri, originalDocumentDetails.name)
64
- copyPath?.let {
65
- originalDocumentDetails.copy(uri = it)
66
- } ?: throw FailedToCopyToCacheException()
63
+ handleSingleSelection(intent)
67
64
  }
68
- }?.let { details ->
69
- val result = DocumentPickerResult(
70
- type = "success",
71
- uri = details.uri,
72
- name = details.name,
73
- mimeType = details.mimeType,
74
- size = details.size
75
- )
76
- promise.resolve(result)
77
- } ?: throw FailedToReadDocumentException()
65
+ } catch (e: CodedException) {
66
+ promise.resolve(e)
67
+ }
78
68
  } else {
79
69
  promise.resolve(
80
- DocumentPickerCancelled(type = "cancel")
70
+ DocumentPickerResult(canceled = true)
81
71
  )
82
72
  }
73
+
74
+ pendingPromise = null
83
75
  }
84
76
  }
85
77
 
@@ -102,4 +94,50 @@ class DocumentPickerModule : Module() {
102
94
  }
103
95
  return Uri.fromFile(outputFile).toString()
104
96
  }
97
+
98
+ private fun handleSingleSelection(intent: Intent?) {
99
+ intent?.data?.let { uri ->
100
+ val details = readDocumentDetails(uri)
101
+ val result = DocumentPickerResult(
102
+ assets = listOf(details),
103
+ )
104
+ pendingPromise?.resolve(result)
105
+ } ?: throw FailedToReadDocumentException()
106
+ }
107
+
108
+ private fun handleMultipleSelection(intent: Intent?) {
109
+ val count = intent?.clipData?.itemCount ?: 0
110
+ val assets = mutableListOf<DocumentInfo>()
111
+
112
+ for (i in 0 until count) {
113
+ val uri = intent?.clipData?.getItemAt(i)?.uri
114
+ ?: throw FailedToReadDocumentException()
115
+ val document = readDocumentDetails(uri)
116
+ assets.add(document)
117
+ }
118
+
119
+ pendingPromise?.resolve(DocumentPickerResult(assets = assets))
120
+ }
121
+
122
+ private fun readDocumentDetails(uri: Uri): DocumentInfo {
123
+ val originalDocumentDetails = DocumentDetailsReader(context).read(uri)
124
+
125
+ val details = if (!copyToCacheDirectory || originalDocumentDetails == null) {
126
+ originalDocumentDetails
127
+ } else {
128
+ val copyPath = copyDocumentToCacheDirectory(uri, originalDocumentDetails.name)
129
+ copyPath?.let {
130
+ originalDocumentDetails.copy(uri = it)
131
+ } ?: throw FailedToCopyToCacheException()
132
+ }
133
+
134
+ return details?.let { it ->
135
+ DocumentInfo(
136
+ uri = it.uri,
137
+ name = it.name,
138
+ mimeType = it.mimeType,
139
+ size = it.size
140
+ )
141
+ } ?: throw FailedToReadDocumentException()
142
+ }
105
143
  }
@@ -10,5 +10,8 @@ data class DocumentPickerOptions(
10
10
 
11
11
  @Field
12
12
  @IsNotEmpty
13
- val type: List<String>
13
+ val type: List<String>,
14
+
15
+ @Field
16
+ val multiple: Boolean
14
17
  ) : Record
@@ -5,8 +5,13 @@ import expo.modules.kotlin.records.Record
5
5
 
6
6
  data class DocumentPickerResult(
7
7
  @Field
8
- val type: String,
8
+ val canceled: Boolean = false,
9
9
 
10
+ @Field
11
+ val assets: List<DocumentInfo>? = null
12
+ ) : Record
13
+
14
+ data class DocumentInfo(
10
15
  @Field
11
16
  val uri: String,
12
17
 
@@ -19,8 +24,3 @@ data class DocumentPickerResult(
19
24
  @Field
20
25
  val size: Int?
21
26
  ) : Record
22
-
23
- data class DocumentPickerCancelled(
24
- @Field
25
- val type: String
26
- ) : Record
@@ -1,7 +1,7 @@
1
- import { DocumentPickerOptions, DocumentResult } from './types';
1
+ import { DocumentPickerOptions, DocumentPickerResult } from './types';
2
2
  declare const _default: {
3
3
  readonly name: string;
4
- getDocumentAsync({ type, multiple, }: DocumentPickerOptions): Promise<DocumentResult>;
4
+ getDocumentAsync({ type, multiple, }: DocumentPickerOptions): Promise<DocumentPickerResult>;
5
5
  };
6
6
  export default _default;
7
7
  //# sourceMappingURL=ExpoDocumentPicker.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoDocumentPicker.web.d.ts","sourceRoot":"","sources":["../src/ExpoDocumentPicker.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;;;0CAU3D,qBAAqB,GAAG,QAAQ,cAAc,CAAC;;AARpD,wBA4DE"}
1
+ {"version":3,"file":"ExpoDocumentPicker.web.d.ts","sourceRoot":"","sources":["../src/ExpoDocumentPicker.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;;;0CAUjE,qBAAqB,GAAG,QAAQ,oBAAoB,CAAC;;AAR1D,wBA8DE"}
@@ -6,7 +6,7 @@ export default {
6
6
  async getDocumentAsync({ type = '*/*', multiple = false, }) {
7
7
  // SSR guard
8
8
  if (!Platform.isDOMAvailable) {
9
- return { type: 'cancel' };
9
+ return { canceled: true, assets: null };
10
10
  }
11
11
  const input = document.createElement('input');
12
12
  input.style.display = 'none';
@@ -29,9 +29,11 @@ export default {
29
29
  reader.onload = ({ target }) => {
30
30
  const uri = target.result;
31
31
  resolve({
32
+ canceled: false,
32
33
  type: 'success',
33
34
  uri,
34
35
  mimeType,
36
+ assets: [],
35
37
  name: targetFile.name,
36
38
  file: targetFile,
37
39
  lastModified: targetFile.lastModified,
@@ -43,7 +45,7 @@ export default {
43
45
  reader.readAsDataURL(targetFile);
44
46
  }
45
47
  else {
46
- resolve({ type: 'cancel' });
48
+ resolve({ canceled: true, assets: null });
47
49
  }
48
50
  document.body.removeChild(input);
49
51
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoDocumentPicker.web.js","sourceRoot":"","sources":["../src/ExpoDocumentPicker.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI7C,eAAe;IACb,IAAI,IAAI;QACN,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EACrB,IAAI,GAAG,KAAK,EACZ,QAAQ,GAAG,KAAK,GACM;QACtB,YAAY;QACZ,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE;YAC5B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;SAC3B;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1E,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChD,IAAI,QAAQ,EAAE;YACZ,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;SAC5C;QAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACpC,IAAI,KAAK,CAAC,KAAK,EAAE;oBACf,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAClC,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC;oBACjC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;oBAChC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;wBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC,CAAC;oBACvF,CAAC,CAAC;oBACF,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;wBAC7B,MAAM,GAAG,GAAI,MAAc,CAAC,MAAM,CAAC;wBACnC,OAAO,CAAC;4BACN,IAAI,EAAE,SAAS;4BACf,GAAG;4BACH,QAAQ;4BACR,IAAI,EAAE,UAAU,CAAC,IAAI;4BACrB,IAAI,EAAE,UAAU;4BAChB,YAAY,EAAE,UAAU,CAAC,YAAY;4BACrC,IAAI,EAAE,UAAU,CAAC,IAAI;4BACrB,MAAM,EAAE,KAAK,CAAC,KAAK;yBACpB,CAAC,CAAC;oBACL,CAAC,CAAC;oBACF,6CAA6C;oBAC7C,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;iBAClC;qBAAM;oBACL,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;iBAC7B;gBAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;YACtC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC","sourcesContent":["import { Platform } from 'expo-modules-core';\n\nimport { DocumentPickerOptions, DocumentResult } from './types';\n\nexport default {\n get name(): string {\n return 'ExpoDocumentPicker';\n },\n\n async getDocumentAsync({\n type = '*/*',\n multiple = false,\n }: DocumentPickerOptions): Promise<DocumentResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { type: 'cancel' };\n }\n\n const input = document.createElement('input');\n input.style.display = 'none';\n input.setAttribute('type', 'file');\n input.setAttribute('accept', Array.isArray(type) ? type.join(',') : type);\n input.setAttribute('id', String(Math.random()));\n if (multiple) {\n input.setAttribute('multiple', 'multiple');\n }\n\n document.body.appendChild(input);\n\n return new Promise((resolve, reject) => {\n input.addEventListener('change', () => {\n if (input.files) {\n const targetFile = input.files[0];\n const mimeType = targetFile.type;\n const reader = new FileReader();\n reader.onerror = () => {\n reject(new Error(`Failed to read the selected media because the operation failed.`));\n };\n reader.onload = ({ target }) => {\n const uri = (target as any).result;\n resolve({\n type: 'success',\n uri,\n mimeType,\n name: targetFile.name,\n file: targetFile,\n lastModified: targetFile.lastModified,\n size: targetFile.size,\n output: input.files,\n });\n };\n // Read in the image file as a binary string.\n reader.readAsDataURL(targetFile);\n } else {\n resolve({ type: 'cancel' });\n }\n\n document.body.removeChild(input);\n });\n\n const event = new MouseEvent('click');\n input.dispatchEvent(event);\n });\n },\n};\n"]}
1
+ {"version":3,"file":"ExpoDocumentPicker.web.js","sourceRoot":"","sources":["../src/ExpoDocumentPicker.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI7C,eAAe;IACb,IAAI,IAAI;QACN,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EACrB,IAAI,GAAG,KAAK,EACZ,QAAQ,GAAG,KAAK,GACM;QACtB,YAAY;QACZ,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE;YAC5B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACzC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC7B,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1E,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAChD,IAAI,QAAQ,EAAE;YACZ,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;SAC5C;QAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACpC,IAAI,KAAK,CAAC,KAAK,EAAE;oBACf,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAClC,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC;oBACjC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;oBAChC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;wBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC,CAAC;oBACvF,CAAC,CAAC;oBACF,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;wBAC7B,MAAM,GAAG,GAAI,MAAc,CAAC,MAAM,CAAC;wBACnC,OAAO,CAAC;4BACN,QAAQ,EAAE,KAAK;4BACf,IAAI,EAAE,SAAS;4BACf,GAAG;4BACH,QAAQ;4BACR,MAAM,EAAE,EAAE;4BACV,IAAI,EAAE,UAAU,CAAC,IAAI;4BACrB,IAAI,EAAE,UAAU;4BAChB,YAAY,EAAE,UAAU,CAAC,YAAY;4BACrC,IAAI,EAAE,UAAU,CAAC,IAAI;4BACrB,MAAM,EAAE,KAAK,CAAC,KAAK;yBACpB,CAAC,CAAC;oBACL,CAAC,CAAC;oBACF,6CAA6C;oBAC7C,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;iBAClC;qBAAM;oBACL,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC3C;gBAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC;YACtC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC","sourcesContent":["import { Platform } from 'expo-modules-core';\n\nimport { DocumentPickerOptions, DocumentPickerResult } from './types';\n\nexport default {\n get name(): string {\n return 'ExpoDocumentPicker';\n },\n\n async getDocumentAsync({\n type = '*/*',\n multiple = false,\n }: DocumentPickerOptions): Promise<DocumentPickerResult> {\n // SSR guard\n if (!Platform.isDOMAvailable) {\n return { canceled: true, assets: null };\n }\n\n const input = document.createElement('input');\n input.style.display = 'none';\n input.setAttribute('type', 'file');\n input.setAttribute('accept', Array.isArray(type) ? type.join(',') : type);\n input.setAttribute('id', String(Math.random()));\n if (multiple) {\n input.setAttribute('multiple', 'multiple');\n }\n\n document.body.appendChild(input);\n\n return new Promise((resolve, reject) => {\n input.addEventListener('change', () => {\n if (input.files) {\n const targetFile = input.files[0];\n const mimeType = targetFile.type;\n const reader = new FileReader();\n reader.onerror = () => {\n reject(new Error(`Failed to read the selected media because the operation failed.`));\n };\n reader.onload = ({ target }) => {\n const uri = (target as any).result;\n resolve({\n canceled: false,\n type: 'success',\n uri,\n mimeType,\n assets: [],\n name: targetFile.name,\n file: targetFile,\n lastModified: targetFile.lastModified,\n size: targetFile.size,\n output: input.files,\n });\n };\n // Read in the image file as a binary string.\n reader.readAsDataURL(targetFile);\n } else {\n resolve({ canceled: true, assets: null });\n }\n\n document.body.removeChild(input);\n });\n\n const event = new MouseEvent('click');\n input.dispatchEvent(event);\n });\n },\n};\n"]}
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DocumentPickerOptions, DocumentResult } from './types';
2
- export { DocumentPickerOptions, DocumentResult };
1
+ import { DocumentPickerOptions, DocumentPickerResult } from './types';
2
+ export { DocumentPickerOptions, DocumentPickerResult as DocumentResult };
3
3
  /**
4
4
  * Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory](filesystem.md#filesystemcachedirectory).
5
5
  * > **Notes for Web:** The system UI can only be shown after user activation (e.g. a `Button` press).
@@ -11,5 +11,5 @@ export { DocumentPickerOptions, DocumentResult };
11
11
  *
12
12
  * If the user cancelled the document picking, the promise resolves to `{ type: 'cancel' }`.
13
13
  */
14
- export declare function getDocumentAsync({ type, copyToCacheDirectory, multiple, }?: DocumentPickerOptions): Promise<DocumentResult>;
14
+ export declare function getDocumentAsync({ type, copyToCacheDirectory, multiple, }?: DocumentPickerOptions): Promise<DocumentPickerResult>;
15
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,CAAC;AAGjD;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,IAAY,EACZ,oBAA2B,EAC3B,QAAgB,GACjB,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAKtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,IAAI,cAAc,EAAE,CAAC;AAqCzE;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,EACrC,IAAY,EACZ,oBAA2B,EAC3B,QAAgB,GACjB,GAAE,qBAA0B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAU5D"}
package/build/index.js CHANGED
@@ -1,4 +1,32 @@
1
1
  import ExpoDocumentPicker from './ExpoDocumentPicker';
2
+ const DEPRECATED_RESULT_KEYS = [
3
+ 'name',
4
+ 'size',
5
+ 'uri',
6
+ 'mimeType',
7
+ 'lastModified',
8
+ 'file',
9
+ 'output',
10
+ ];
11
+ function mergeDeprecatedResult(result) {
12
+ const firstAsset = result.assets?.[0];
13
+ const deprecatedResult = {
14
+ ...result,
15
+ get type() {
16
+ console.warn('Key "type" in the document picker result is deprecated and will be removed in SDK 49, use "canceled" instead');
17
+ return this.canceled ? 'cancel' : 'success';
18
+ },
19
+ };
20
+ for (const key of DEPRECATED_RESULT_KEYS) {
21
+ Object.defineProperty(deprecatedResult, key, {
22
+ get() {
23
+ console.warn(`Key "${key}" in the document picker result is deprecated and will be removed in SDK 49, you can access selected assets through the "assets" array instead`);
24
+ return firstAsset?.[key];
25
+ },
26
+ });
27
+ }
28
+ return deprecatedResult;
29
+ }
2
30
  // @needsAudit
3
31
  /**
4
32
  * Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory](filesystem.md#filesystemcachedirectory).
@@ -15,6 +43,11 @@ export async function getDocumentAsync({ type = '*/*', copyToCacheDirectory = tr
15
43
  if (typeof type === 'string') {
16
44
  type = [type];
17
45
  }
18
- return await ExpoDocumentPicker.getDocumentAsync({ type, copyToCacheDirectory, multiple });
46
+ const result = await ExpoDocumentPicker.getDocumentAsync({
47
+ type,
48
+ copyToCacheDirectory,
49
+ multiple,
50
+ });
51
+ return mergeDeprecatedResult(result);
19
52
  }
20
53
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AAItD,cAAc;AACd;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EACrC,IAAI,GAAG,KAAK,EACZ,oBAAoB,GAAG,IAAI,EAC3B,QAAQ,GAAG,KAAK,MACS,EAAE;IAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QAC5B,IAAI,GAAG,CAAC,IAAI,CAAa,CAAC;KAC3B;IACD,OAAO,MAAM,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC7F,CAAC","sourcesContent":["import ExpoDocumentPicker from './ExpoDocumentPicker';\nimport { DocumentPickerOptions, DocumentResult } from './types';\nexport { DocumentPickerOptions, DocumentResult };\n\n// @needsAudit\n/**\n * Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory](filesystem.md#filesystemcachedirectory).\n * > **Notes for Web:** The system UI can only be shown after user activation (e.g. a `Button` press).\n * > Therefore, calling `getDocumentAsync` in `componentDidMount`, for example, will **not** work as\n * > intended. The `cancel` event will not be returned in the browser due to platform restrictions and\n * > inconsistencies across browsers.\n *\n * @return On success returns a promise that fulfils with [`DocumentResult`](#documentresult) object.\n *\n * If the user cancelled the document picking, the promise resolves to `{ type: 'cancel' }`.\n */\nexport async function getDocumentAsync({\n type = '*/*',\n copyToCacheDirectory = true,\n multiple = false,\n}: DocumentPickerOptions = {}): Promise<DocumentResult> {\n if (typeof type === 'string') {\n type = [type] as string[];\n }\n return await ExpoDocumentPicker.getDocumentAsync({ type, copyToCacheDirectory, multiple });\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AAItD,MAAM,sBAAsB,GAAG;IAC7B,MAAM;IACN,MAAM;IACN,KAAK;IACL,UAAU;IACV,cAAc;IACd,MAAM;IACN,QAAQ;CACT,CAAC;AAEF,SAAS,qBAAqB,CAAC,MAA4B;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,gBAAgB,GAAG;QACvB,GAAG,MAAM;QACT,IAAI,IAAI;YACN,OAAO,CAAC,IAAI,CACV,8GAA8G,CAC/G,CAAC;YACF,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9C,CAAC;KACF,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,sBAAsB,EAAE;QACxC,MAAM,CAAC,cAAc,CAAC,gBAAgB,EAAE,GAAG,EAAE;YAC3C,GAAG;gBACD,OAAO,CAAC,IAAI,CACV,QAAQ,GAAG,gJAAgJ,CAC5J,CAAC;gBACF,OAAO,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;SACF,CAAC,CAAC;KACJ;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,cAAc;AACd;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EACrC,IAAI,GAAG,KAAK,EACZ,oBAAoB,GAAG,IAAI,EAC3B,QAAQ,GAAG,KAAK,MACS,EAAE;IAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QAC5B,IAAI,GAAG,CAAC,IAAI,CAAa,CAAC;KAC3B;IACD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,gBAAgB,CAAC;QACvD,IAAI;QACJ,oBAAoB;QACpB,QAAQ;KACT,CAAC,CAAC;IACH,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import ExpoDocumentPicker from './ExpoDocumentPicker';\nimport { DocumentPickerOptions, DocumentPickerResult } from './types';\nexport { DocumentPickerOptions, DocumentPickerResult as DocumentResult };\n\nconst DEPRECATED_RESULT_KEYS = [\n 'name',\n 'size',\n 'uri',\n 'mimeType',\n 'lastModified',\n 'file',\n 'output',\n];\n\nfunction mergeDeprecatedResult(result: DocumentPickerResult): DocumentPickerResult {\n const firstAsset = result.assets?.[0];\n const deprecatedResult = {\n ...result,\n get type() {\n console.warn(\n 'Key \"type\" in the document picker result is deprecated and will be removed in SDK 49, use \"canceled\" instead'\n );\n return this.canceled ? 'cancel' : 'success';\n },\n };\n for (const key of DEPRECATED_RESULT_KEYS) {\n Object.defineProperty(deprecatedResult, key, {\n get() {\n console.warn(\n `Key \"${key}\" in the document picker result is deprecated and will be removed in SDK 49, you can access selected assets through the \"assets\" array instead`\n );\n return firstAsset?.[key];\n },\n });\n }\n return deprecatedResult;\n}\n\n// @needsAudit\n/**\n * Display the system UI for choosing a document. By default, the chosen file is copied to [the app's internal cache directory](filesystem.md#filesystemcachedirectory).\n * > **Notes for Web:** The system UI can only be shown after user activation (e.g. a `Button` press).\n * > Therefore, calling `getDocumentAsync` in `componentDidMount`, for example, will **not** work as\n * > intended. The `cancel` event will not be returned in the browser due to platform restrictions and\n * > inconsistencies across browsers.\n *\n * @return On success returns a promise that fulfils with [`DocumentResult`](#documentresult) object.\n *\n * If the user cancelled the document picking, the promise resolves to `{ type: 'cancel' }`.\n */\nexport async function getDocumentAsync({\n type = '*/*',\n copyToCacheDirectory = true,\n multiple = false,\n}: DocumentPickerOptions = {}): Promise<DocumentPickerResult> {\n if (typeof type === 'string') {\n type = [type] as string[];\n }\n const result = await ExpoDocumentPicker.getDocumentAsync({\n type,\n copyToCacheDirectory,\n multiple,\n });\n return mergeDeprecatedResult(result);\n}\n"]}
package/build/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type DocumentPickerOptions = {
2
2
  /**
3
3
  * The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available
4
- * to be picked. Is also supports wildcards like `'image/*'` to choose any image. To allow any type
4
+ * to be picked. It also supports wildcards like `'image/*'` to choose any image. To allow any type
5
5
  * of document you can use `'&ast;/*'`.
6
6
  * @default '&ast;/*'
7
7
  */
@@ -17,36 +17,65 @@ export type DocumentPickerOptions = {
17
17
  /**
18
18
  * Allows multiple files to be selected from the system UI.
19
19
  * @default false
20
- * @platform web
20
+ *
21
21
  */
22
22
  multiple?: boolean;
23
23
  };
24
- /**
25
- * First object represents the result when the document pick has been cancelled.
26
- * The second one represents the successful document pick result.
27
- */
28
- export type DocumentResult = {
24
+ export type DocumentPickerAsset = {
29
25
  /**
30
- * Field indicating that the document pick has been cancelled.
26
+ * Document original name.
31
27
  */
32
- type: 'cancel';
33
- } | {
28
+ name: string;
34
29
  /**
35
- * Field indicating that the document pick has been successful.
30
+ * Document size in bytes.
36
31
  */
37
- type: 'success';
32
+ size?: number;
33
+ /**
34
+ * An URI to the local document file.
35
+ */
36
+ uri: string;
37
+ /**
38
+ * Document MIME type.
39
+ */
40
+ mimeType?: string;
41
+ /**
42
+ * Timestamp of last document modification.
43
+ */
44
+ lastModified?: number;
45
+ /**
46
+ * `File` object for the parity with web File API.
47
+ * @platform web
48
+ */
49
+ file?: File;
50
+ /**
51
+ * `FileList` object for the parity with web File API.
52
+ * @platform web
53
+ */
54
+ output?: FileList | null;
55
+ };
56
+ export type DocumentPickerResult = {
57
+ /**
58
+ * Boolean flag which shows if request was canceled. If asset data have been returned this should
59
+ * always be `false`.
60
+ */
61
+ canceled: boolean;
62
+ type?: string;
38
63
  /**
39
64
  * Document original name.
40
65
  */
41
- name: string;
66
+ name?: string;
42
67
  /**
43
68
  * Document size in bytes.
44
69
  */
45
70
  size?: number;
71
+ /**
72
+ * An array of picked assets or `null` when the request was canceled.
73
+ */
74
+ assets: DocumentPickerAsset[] | null;
46
75
  /**
47
76
  * An URI to the local document file.
48
77
  */
49
- uri: string;
78
+ uri?: string;
50
79
  /**
51
80
  * Document MIME type.
52
81
  */
@@ -65,5 +94,19 @@ export type DocumentResult = {
65
94
  * @platform web
66
95
  */
67
96
  output?: FileList | null;
97
+ } & (DocumentPickerSuccessResult | DocumentPickerCanceledResult);
98
+ /**
99
+ * @hidden
100
+ */
101
+ export type DocumentPickerSuccessResult = {
102
+ canceled: false;
103
+ assets: DocumentPickerAsset[];
104
+ };
105
+ /**
106
+ * @hidden
107
+ */
108
+ export type DocumentPickerCanceledResult = {
109
+ canceled: true;
110
+ assets: null;
68
111
  };
69
112
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAGF;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IACE;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAC;CAChB,GACD;IACE;;OAEG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC1B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC1B,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,MAAM,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC;IACrC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC1B,GAAG,CAAC,2BAA2B,GAAG,4BAA4B,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,KAAK,CAAC;IAChB,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;CACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["// @needsAudit\nexport type DocumentPickerOptions = {\n /**\n * The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\n * to be picked. Is also supports wildcards like `'image/*'` to choose any image. To allow any type\n * of document you can use `'&ast;/*'`.\n * @default '&ast;/*'\n */\n type?: string | string[];\n /**\n * If `true`, the picked file is copied to [`FileSystem.CacheDirectory`](./filesystem#filesystemcachedirectory),\n * which allows other Expo APIs to read the file immediately. This may impact performance for\n * large files, so you should consider setting this to `false` if you expect users to pick\n * particularly large files and your app does not need immediate read access.\n * @default true\n */\n copyToCacheDirectory?: boolean;\n /**\n * Allows multiple files to be selected from the system UI.\n * @default false\n * @platform web\n */\n multiple?: boolean;\n};\n\n// @needsAudit @docsMissing\n/**\n * First object represents the result when the document pick has been cancelled.\n * The second one represents the successful document pick result.\n */\nexport type DocumentResult =\n | {\n /**\n * Field indicating that the document pick has been cancelled.\n */\n type: 'cancel';\n }\n | {\n /**\n * Field indicating that the document pick has been successful.\n */\n type: 'success';\n /**\n * Document original name.\n */\n name: string;\n /**\n * Document size in bytes.\n */\n size?: number;\n /**\n * An URI to the local document file.\n */\n uri: string;\n /**\n * Document MIME type.\n */\n mimeType?: string;\n /**\n * Timestamp of last document modification.\n */\n lastModified?: number;\n /**\n * `File` object for the parity with web File API.\n * @platform web\n */\n file?: File;\n /**\n * `FileList` object for the parity with web File API.\n * @platform web\n */\n output?: FileList | null;\n };\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["// @needsAudit\nexport type DocumentPickerOptions = {\n /**\n * The [MIME type(s)](https://en.wikipedia.org/wiki/Media_type) of the documents that are available\n * to be picked. It also supports wildcards like `'image/*'` to choose any image. To allow any type\n * of document you can use `'&ast;/*'`.\n * @default '&ast;/*'\n */\n type?: string | string[];\n /**\n * If `true`, the picked file is copied to [`FileSystem.CacheDirectory`](./filesystem#filesystemcachedirectory),\n * which allows other Expo APIs to read the file immediately. This may impact performance for\n * large files, so you should consider setting this to `false` if you expect users to pick\n * particularly large files and your app does not need immediate read access.\n * @default true\n */\n copyToCacheDirectory?: boolean;\n /**\n * Allows multiple files to be selected from the system UI.\n * @default false\n *\n */\n multiple?: boolean;\n};\n\nexport type DocumentPickerAsset = {\n /**\n * Document original name.\n */\n name: string;\n /**\n * Document size in bytes.\n */\n size?: number;\n /**\n * An URI to the local document file.\n */\n uri: string;\n /**\n * Document MIME type.\n */\n mimeType?: string;\n /**\n * Timestamp of last document modification.\n */\n lastModified?: number;\n /**\n * `File` object for the parity with web File API.\n * @platform web\n */\n file?: File;\n /**\n * `FileList` object for the parity with web File API.\n * @platform web\n */\n output?: FileList | null;\n};\n\n// @needsAudit @docsMissing\nexport type DocumentPickerResult = {\n /**\n * Boolean flag which shows if request was canceled. If asset data have been returned this should\n * always be `false`.\n */\n canceled: boolean;\n type?: string;\n /**\n * Document original name.\n */\n name?: string;\n /**\n * Document size in bytes.\n */\n size?: number;\n /**\n * An array of picked assets or `null` when the request was canceled.\n */\n assets: DocumentPickerAsset[] | null;\n /**\n * An URI to the local document file.\n */\n uri?: string;\n /**\n * Document MIME type.\n */\n mimeType?: string;\n /**\n * Timestamp of last document modification.\n */\n lastModified?: number;\n /**\n * `File` object for the parity with web File API.\n * @platform web\n */\n file?: File;\n /**\n * `FileList` object for the parity with web File API.\n * @platform web\n */\n output?: FileList | null;\n} & (DocumentPickerSuccessResult | DocumentPickerCanceledResult);\n\n/**\n * @hidden\n */\nexport type DocumentPickerSuccessResult = {\n canceled: false;\n assets: DocumentPickerAsset[];\n};\n\n/**\n * @hidden\n */\nexport type DocumentPickerCanceledResult = {\n canceled: true;\n assets: null;\n};\n"]}
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "expo-document-picker",
3
3
  "platforms": ["ios", "android"],
4
+ "ios": {
5
+ "modules": ["DocumentPickerModule"]
6
+ },
4
7
  "android": {
5
8
  "modules": ["expo.modules.documentpicker.DocumentPickerModule"]
6
9
  }
@@ -0,0 +1,172 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+ import MobileCoreServices
4
+
5
+ struct PickingContext {
6
+ let promise: Promise
7
+ let options: DocumentPickerOptions
8
+ let delegate: DocumentPickingDelegate
9
+ }
10
+
11
+ public class DocumentPickerModule: Module, PickingResultHandler {
12
+ private var pickingContext: PickingContext?
13
+
14
+ public func definition() -> ModuleDefinition {
15
+ Name("ExpoDocumentPicker")
16
+
17
+ AsyncFunction("getDocumentAsync") { (options: DocumentPickerOptions, promise: Promise) in
18
+ if pickingContext != nil {
19
+ throw PickingInProgressException()
20
+ }
21
+
22
+ guard let currentVc = appContext?.utilities?.currentViewController() else {
23
+ throw MissingViewControllerException()
24
+ }
25
+
26
+ let documentPickerVC = createDocumentPicker(with: options)
27
+ let pickerDelegate = DocumentPickingDelegate(resultHandler: self)
28
+
29
+ pickingContext = PickingContext(promise: promise, options: options, delegate: pickerDelegate)
30
+
31
+ documentPickerVC.delegate = pickerDelegate
32
+ documentPickerVC.presentationController?.delegate = pickerDelegate
33
+ documentPickerVC.allowsMultipleSelection = options.multiple
34
+
35
+ if UIDevice.current.userInterfaceIdiom == .pad {
36
+ let viewFrame = currentVc.view.frame
37
+ documentPickerVC.popoverPresentationController?.sourceRect = CGRect(
38
+ x: viewFrame.midX,
39
+ y: viewFrame.maxY,
40
+ width: 0,
41
+ height: 0
42
+ )
43
+ documentPickerVC.popoverPresentationController?.sourceView = currentVc.view
44
+ documentPickerVC.modalPresentationStyle = .pageSheet
45
+ }
46
+ currentVc.present(documentPickerVC, animated: true)
47
+ }.runOnQueue(.main)
48
+ }
49
+
50
+ func didPickDocumentsAt(urls: [URL]) {
51
+ guard let options = self.pickingContext?.options,
52
+ let promise = self.pickingContext?.promise else {
53
+ log.error("Picking context has been lost.")
54
+ return
55
+ }
56
+ pickingContext = nil
57
+
58
+ do {
59
+ if options.multiple {
60
+ let assets = try urls.map {
61
+ try readDocumentDetails(
62
+ documentUrl: $0,
63
+ copy: options.copyToCacheDirectory
64
+ )
65
+ }
66
+ promise.resolve(DocumentPickerResponse(assets: assets))
67
+ } else {
68
+ let asset = try readDocumentDetails(
69
+ documentUrl: urls[0],
70
+ copy: options.copyToCacheDirectory
71
+ )
72
+ promise.resolve(DocumentPickerResponse(assets: [asset]))
73
+ }
74
+ } catch {
75
+ promise.reject(error)
76
+ }
77
+ }
78
+
79
+ func didCancelPicking() {
80
+ guard let context = pickingContext else {
81
+ log.error("Picking context lost")
82
+ return
83
+ }
84
+
85
+ pickingContext = nil
86
+ context.promise.resolve(DocumentPickerResponse(canceled: true))
87
+ }
88
+
89
+ private func getFileSize(path: URL) -> Int? {
90
+ guard let resource = try? path.resourceValues(forKeys: [.fileSizeKey, .isDirectoryKey]) else {
91
+ return 0
92
+ }
93
+
94
+ if let isDirectory = resource.isDirectory {
95
+ if !isDirectory {
96
+ return resource.fileSize
97
+ }
98
+ }
99
+
100
+ guard let contents = try? FileManager.default.contentsOfDirectory(atPath: path.absoluteString) else {
101
+ return 0
102
+ }
103
+
104
+ let folderSize = contents.reduce(0) { currentSize, file in
105
+ let fileSize = getFileSize(path: path.appendingPathComponent(file)) ?? 0
106
+ return currentSize + fileSize
107
+ }
108
+
109
+ return folderSize
110
+ }
111
+
112
+ private func readDocumentDetails(documentUrl: URL, copy: Bool) throws -> DocumentInfo {
113
+ let pathExtension = documentUrl.pathExtension
114
+ var newUrl = documentUrl
115
+
116
+ guard let fileSystem = self.appContext?.fileSystem else {
117
+ throw Exceptions.FileSystemModuleNotFound()
118
+ }
119
+
120
+ guard let fileSize = try? getFileSize(path: documentUrl) else {
121
+ throw InvalidFileException()
122
+ }
123
+
124
+ if copy {
125
+ let directory = fileSystem.cachesDirectory.appending("DocumentPicker")
126
+ let path = fileSystem.generatePath(inDirectory: directory, withExtension: pathExtension)
127
+ newUrl = URL(fileURLWithPath: path)
128
+ try FileManager.default.copyItem(at: documentUrl, to: newUrl)
129
+ }
130
+
131
+ let mimeType = self.getMimeType(from: pathExtension)
132
+
133
+ return DocumentInfo(
134
+ uri: newUrl.absoluteString,
135
+ name: documentUrl.lastPathComponent,
136
+ size: fileSize,
137
+ mimeType: mimeType
138
+ )
139
+ }
140
+
141
+ private func getMimeType(from pathExtension: String) -> String? {
142
+ if #available(iOS 14, *) {
143
+ return UTType(filenameExtension: pathExtension)?.preferredMIMEType
144
+ } else {
145
+ if let uti = UTTypeCreatePreferredIdentifierForTag(
146
+ kUTTagClassFilenameExtension,
147
+ pathExtension as NSString, nil
148
+ )?.takeRetainedValue() {
149
+ if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
150
+ return mimetype as String
151
+ }
152
+ }
153
+ return nil
154
+ }
155
+ }
156
+
157
+ private func createDocumentPicker(with options: DocumentPickerOptions) -> UIDocumentPickerViewController {
158
+ if #available(iOS 14.0, *) {
159
+ let utTypes = options.type.compactMap { $0.toUTType() }
160
+ return UIDocumentPickerViewController(
161
+ forOpeningContentTypes: utTypes,
162
+ asCopy: true
163
+ )
164
+ } else {
165
+ let utiTypes = options.type.map { $0.toUTI() }
166
+ return UIDocumentPickerViewController(
167
+ documentTypes: utiTypes,
168
+ in: .import
169
+ )
170
+ }
171
+ }
172
+ }