expo-media-library 18.1.2-canary-20250912-b5ce2a8 → 18.3.0-canary-20250919-7a31b96

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 (124) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/medialibrary/next/MediaLibraryNextModule.kt +73 -0
  4. package/android/src/main/java/expo/modules/medialibrary/next/exceptions/QueryExceptions.kt +6 -0
  5. package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AlbumExtensions.kt +9 -0
  6. package/android/src/main/java/expo/modules/medialibrary/next/extensions/resolver/AssetExtensions.kt +5 -5
  7. package/android/src/main/java/expo/modules/medialibrary/next/objects/album/AlbumQuery.kt +22 -0
  8. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/Asset.kt +2 -1
  9. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetDelegate.kt +2 -1
  10. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetLegacyDelegate.kt +13 -8
  11. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetModernDelegate.kt +13 -8
  12. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/factories/AssetLegacyFactory.kt +1 -1
  13. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/MediaStoreQueryFormatter.kt +31 -0
  14. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/Query.kt +108 -0
  15. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryExecutor.kt +8 -0
  16. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryLegacyExecutor.kt +73 -0
  17. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryModernExecutor.kt +72 -0
  18. package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/MediaType.kt +55 -0
  19. package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/MimeType.kt +10 -3
  20. package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/RelativePath.kt +2 -1
  21. package/android/src/main/java/expo/modules/medialibrary/next/records/AssetField.kt +23 -0
  22. package/android/src/main/java/expo/modules/medialibrary/next/records/SortDescriptor.kt +14 -0
  23. package/build/next/ExpoMediaLibraryNext.d.ts +4 -1
  24. package/build/next/ExpoMediaLibraryNext.d.ts.map +1 -1
  25. package/build/next/ExpoMediaLibraryNext.js.map +1 -1
  26. package/build/next/MediaLibraryNext.types.d.ts +4 -2
  27. package/build/next/MediaLibraryNext.types.d.ts.map +1 -1
  28. package/build/next/MediaLibraryNext.types.js +4 -2
  29. package/build/next/MediaLibraryNext.types.js.map +1 -1
  30. package/build/next/index.d.ts +14 -2
  31. package/build/next/index.d.ts.map +1 -1
  32. package/build/next/index.js +19 -2
  33. package/build/next/index.js.map +1 -1
  34. package/build/next/types/Album.d.ts +38 -17
  35. package/build/next/types/Album.d.ts.map +1 -1
  36. package/build/next/types/Album.js.map +1 -1
  37. package/build/next/types/Asset.d.ts +21 -25
  38. package/build/next/types/Asset.d.ts.map +1 -1
  39. package/build/next/types/Asset.js.map +1 -1
  40. package/build/next/types/AssetField.d.ts +18 -0
  41. package/build/next/types/AssetField.d.ts.map +1 -0
  42. package/build/next/types/AssetField.js +10 -0
  43. package/build/next/types/AssetField.js.map +1 -0
  44. package/build/next/types/GranularPermission.d.ts +2 -0
  45. package/build/next/types/GranularPermission.d.ts.map +1 -0
  46. package/build/next/types/GranularPermission.js +2 -0
  47. package/build/next/types/GranularPermission.js.map +1 -0
  48. package/build/next/types/MediaType.d.ts +7 -0
  49. package/build/next/types/MediaType.d.ts.map +1 -0
  50. package/build/next/types/MediaType.js +8 -0
  51. package/build/next/types/MediaType.js.map +1 -0
  52. package/build/next/types/Query.d.ts +93 -0
  53. package/build/next/types/Query.d.ts.map +1 -0
  54. package/build/next/types/Query.js +2 -0
  55. package/build/next/types/Query.js.map +1 -0
  56. package/build/next/types/SortDescriptor.d.ts +6 -0
  57. package/build/next/types/SortDescriptor.d.ts.map +1 -0
  58. package/build/next/types/SortDescriptor.js +2 -0
  59. package/build/next/types/SortDescriptor.js.map +1 -0
  60. package/expo-module.config.json +1 -1
  61. package/ios/next/MediaLibraryNextModule.swift +74 -8
  62. package/ios/next/exceptions/Exceptions.swift +24 -0
  63. package/ios/next/extensions/DateExtensions.swift +8 -0
  64. package/ios/next/objects/Query/AssetField.swift +27 -0
  65. package/ios/next/objects/Query/PredicateBuilder.swift +50 -0
  66. package/ios/next/objects/Query/Query.swift +135 -0
  67. package/ios/next/objects/Query/SortDescriptor.swift +11 -0
  68. package/ios/next/objects/asset/Asset.swift +7 -7
  69. package/ios/next/objects/asset/MediaType.swift +38 -0
  70. package/ios/next/repository/AssetCollectionRepository.swift +12 -0
  71. package/ios/next/repository/AssetRepository.swift +18 -0
  72. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8-sources.jar → 18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar} +0 -0
  73. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.md5 +1 -0
  74. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha1 +1 -0
  75. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha256 +1 -0
  76. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96-sources.jar.sha512 +1 -0
  77. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar +0 -0
  78. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.md5 +1 -0
  79. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha1 +1 -0
  80. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha256 +1 -0
  81. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.aar.sha512 +1 -0
  82. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.module → 18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module} +22 -22
  83. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.md5 +1 -0
  84. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha1 +1 -0
  85. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha256 +1 -0
  86. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.module.sha512 +1 -0
  87. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.pom → 18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom} +1 -1
  88. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.md5 +1 -0
  89. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha1 +1 -0
  90. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha256 +1 -0
  91. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250919-7a31b96/expo.modules.medialibrary-18.3.0-canary-20250919-7a31b96.pom.sha512 +1 -0
  92. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml +4 -4
  93. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.md5 +1 -1
  94. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha1 +1 -1
  95. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha256 +1 -1
  96. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha512 +1 -1
  97. package/package.json +3 -3
  98. package/src/next/ExpoMediaLibraryNext.ts +4 -1
  99. package/src/next/MediaLibraryNext.types.ts +4 -2
  100. package/src/next/index.ts +24 -4
  101. package/src/next/types/Album.ts +39 -21
  102. package/src/next/types/Asset.ts +30 -19
  103. package/src/next/types/AssetField.ts +19 -0
  104. package/src/next/types/GranularPermission.ts +1 -0
  105. package/src/next/types/MediaType.ts +6 -0
  106. package/src/next/types/Query.ts +96 -0
  107. package/src/next/types/SortDescriptor.ts +6 -0
  108. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8-sources.jar.md5 +0 -1
  109. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8-sources.jar.sha1 +0 -1
  110. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8-sources.jar.sha256 +0 -1
  111. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8-sources.jar.sha512 +0 -1
  112. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.aar +0 -0
  113. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.aar.md5 +0 -1
  114. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.aar.sha1 +0 -1
  115. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.aar.sha256 +0 -1
  116. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.aar.sha512 +0 -1
  117. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.module.md5 +0 -1
  118. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.module.sha1 +0 -1
  119. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.module.sha256 +0 -1
  120. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.module.sha512 +0 -1
  121. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.pom.md5 +0 -1
  122. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.pom.sha1 +0 -1
  123. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.pom.sha256 +0 -1
  124. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.2-canary-20250912-b5ce2a8/expo.modules.medialibrary-18.1.2-canary-20250912-b5ce2a8.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -6,10 +6,23 @@
6
6
 
7
7
  ### 🎉 New features
8
8
 
9
+ - [next] Add `Album.get(title)` ([#39717](https://github.com/expo/expo/pull/39717) by [@Wenszel](https://github.com/Wenszel))
10
+
9
11
  ### 🐛 Bug fixes
10
12
 
13
+ - [next][android] Change default root directory to Pictures ([#39716](https://github.com/expo/expo/pull/39716) by [@Wenszel](https://github.com/Wenszel))
14
+ - [next] Fix `asset.getModificationTime` to return milliseconds ([#39715](https://github.com/expo/expo/pull/39715) by [@Wenszel](https://github.com/Wenszel))
15
+
11
16
  ### 💡 Others
12
17
 
18
+ - [next] Add documentation ([#39754](https://github.com/expo/expo/pull/39754) by [@Wenszel](https://github.com/Wenszel))
19
+
20
+ ## 18.2.0 — 2025-09-16
21
+
22
+ ### 🎉 New features
23
+
24
+ - [next] Add Query ([#39559](https://github.com/expo/expo/pull/39559) by [@Wenszel](https://github.com/Wenszel))
25
+
13
26
  ## 18.1.1 — 2025-09-10
14
27
 
15
28
  ### 💡 Others
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '18.1.2-canary-20250912-b5ce2a8'
7
+ version = '18.3.0-canary-20250919-7a31b96'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.medialibrary"
11
11
  defaultConfig {
12
12
  versionCode 37
13
- versionName "18.1.2-canary-20250912-b5ce2a8"
13
+ versionName "18.3.0-canary-20250919-7a31b96"
14
14
  }
15
15
  }
16
16
 
@@ -11,15 +11,22 @@ import expo.modules.kotlin.modules.ModuleDefinition
11
11
  import expo.modules.kotlin.types.Either
12
12
  import expo.modules.kotlin.types.toKClass
13
13
  import expo.modules.medialibrary.next.objects.album.Album
14
+ import expo.modules.medialibrary.next.objects.album.AlbumQuery
14
15
  import expo.modules.medialibrary.next.objects.asset.Asset
15
16
  import expo.modules.medialibrary.next.objects.album.factories.AlbumModernFactory
16
17
  import expo.modules.medialibrary.next.objects.album.factories.AlbumLegacyFactory
17
18
  import expo.modules.medialibrary.next.objects.asset.factories.AssetModernFactory
18
19
  import expo.modules.medialibrary.next.objects.asset.factories.AssetLegacyFactory
20
+ import expo.modules.medialibrary.next.objects.query.MediaStoreQueryFormatter
21
+ import expo.modules.medialibrary.next.objects.query.Query
22
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
19
23
  import expo.modules.medialibrary.next.permissions.MediaStorePermissionsDelegate
20
24
  import expo.modules.medialibrary.next.permissions.SystemPermissionsDelegate
21
25
  import expo.modules.medialibrary.next.permissions.enums.GranularPermission
26
+ import expo.modules.medialibrary.next.records.AssetField
27
+ import expo.modules.medialibrary.next.records.SortDescriptor
22
28
 
29
+ @OptIn(EitherType::class)
23
30
  class MediaLibraryNextModule : Module() {
24
31
  private val context
25
32
  get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
@@ -32,6 +39,10 @@ class MediaLibraryNextModule : Module() {
32
39
  MediaStorePermissionsDelegate(appContext)
33
40
  }
34
41
 
42
+ private val albumQuery by lazy {
43
+ AlbumQuery(context)
44
+ }
45
+
35
46
  private val albumFactory by lazy {
36
47
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
37
48
  AlbumModernFactory(assetFactory, context)
@@ -141,6 +152,63 @@ class MediaLibraryNextModule : Module() {
141
152
  }
142
153
  }
143
154
 
155
+ Class(Query::class) {
156
+ Constructor {
157
+ Query(context)
158
+ }
159
+
160
+ Function("limit") { self: Query, limit: Int ->
161
+ self.limit(limit)
162
+ }
163
+
164
+ Function("offset") { self: Query, offset: Int ->
165
+ self.offset(offset)
166
+ }
167
+
168
+ Function("album") { self: Query, album: Album ->
169
+ self.album(album)
170
+ }
171
+
172
+ Function("eq") { self: Query, field: AssetField, value: Either<MediaType, Long> ->
173
+ self.eq(field, MediaStoreQueryFormatter.parse(field, value))
174
+ }
175
+
176
+ Function("within") { self: Query, field: AssetField, values: List<Either<MediaType, Long>> ->
177
+ val stringValues = values.map { value -> MediaStoreQueryFormatter.parse(field, value) }
178
+ self.within(field, stringValues)
179
+ }
180
+
181
+ Function("gt") { self: Query, field: AssetField, value: Long ->
182
+ self.gt(field, MediaStoreQueryFormatter.parse(field, value))
183
+ }
184
+
185
+ Function("gte") { self: Query, field: AssetField, value: Long ->
186
+ self.gte(field, MediaStoreQueryFormatter.parse(field, value))
187
+ }
188
+
189
+ Function("lt") { self: Query, field: AssetField, value: Long ->
190
+ self.lt(field, MediaStoreQueryFormatter.parse(field, value))
191
+ }
192
+
193
+ Function("lte") { self: Query, field: AssetField, value: Long ->
194
+ self.lte(field, MediaStoreQueryFormatter.parse(field, value))
195
+ }
196
+
197
+ Function("orderBy") { self: Query, sortDescriptorRef: Either<AssetField, SortDescriptor> ->
198
+ if (sortDescriptorRef.`is`(AssetField::class)) {
199
+ val assetField = sortDescriptorRef.get(AssetField::class)
200
+ val descriptor = SortDescriptor(assetField)
201
+ return@Function self.orderBy(descriptor)
202
+ }
203
+ val descriptor = sortDescriptorRef.get(SortDescriptor::class)
204
+ return@Function self.orderBy(descriptor)
205
+ }
206
+
207
+ AsyncFunction("exe") Coroutine { self: Query ->
208
+ return@Coroutine self.exe()
209
+ }
210
+ }
211
+
144
212
  AsyncFunction("createAsset") Coroutine { filePath: Uri, album: Album? ->
145
213
  systemPermissionsDelegate.requireSystemPermissions(true)
146
214
  return@Coroutine assetFactory.create(filePath, album?.getRelativePath())
@@ -158,6 +226,11 @@ class MediaLibraryNextModule : Module() {
158
226
  return@Coroutine albumFactory.createFromFilePaths(name, assetPaths)
159
227
  }
160
228
 
229
+ AsyncFunction("getAlbum") Coroutine { title: String ->
230
+ systemPermissionsDelegate.requireSystemPermissions(false)
231
+ albumQuery.getAlbum(title)
232
+ }
233
+
161
234
  AsyncFunction("deleteAlbums") Coroutine { albums: List<Album> ->
162
235
  systemPermissionsDelegate.requireSystemPermissions(true)
163
236
  albums.forEach { album -> album.delete() }
@@ -0,0 +1,6 @@
1
+ package expo.modules.medialibrary.next.exceptions
2
+
3
+ import expo.modules.kotlin.exception.CodedException
4
+
5
+ class QueryCouldNotBeExecuted(message: String, cause: Throwable? = null) :
6
+ CodedException("Could not execute a query $message", cause)
@@ -48,6 +48,15 @@ suspend fun ContentResolver.queryAlbumId(relativePath: RelativePath): String? =
48
48
  arrayOf(relativePath.value)
49
49
  )
50
50
 
51
+ suspend fun ContentResolver.queryAlbumId(name: String): String? =
52
+ queryOne(
53
+ EXTERNAL_CONTENT_URI,
54
+ MediaStore.Files.FileColumns.BUCKET_ID,
55
+ Cursor::getString,
56
+ "${MediaStore.MediaColumns.BUCKET_DISPLAY_NAME} = ?",
57
+ arrayOf(name)
58
+ )
59
+
51
60
  suspend fun ContentResolver.queryAlbumAssetsContentUris(bucketId: String): List<Uri> =
52
61
  withContext(Dispatchers.IO) {
53
62
  val projection = arrayOf(
@@ -17,26 +17,26 @@ import kotlinx.coroutines.withContext
17
17
  suspend fun ContentResolver.queryAssetDisplayName(contentUri: Uri): String? =
18
18
  queryOne(contentUri, MediaStore.MediaColumns.DISPLAY_NAME, Cursor::getString)
19
19
 
20
- suspend fun ContentResolver.queryGetCreationTime(contentUri: Uri): Long? =
20
+ suspend fun ContentResolver.queryAssetCreationTime(contentUri: Uri): Long? =
21
21
  queryOne(contentUri, MediaStore.Images.Media.DATE_TAKEN, Cursor::getLong)
22
22
 
23
23
  suspend fun ContentResolver.queryAssetModificationTime(contentUri: Uri): Long? =
24
- queryOne(contentUri, MediaStore.Images.Media.DATE_MODIFIED, Cursor::getLong)
24
+ queryOne(contentUri, MediaStore.MediaColumns.DATE_MODIFIED, Cursor::getLong)
25
25
 
26
26
  suspend fun ContentResolver.queryAssetDuration(contentUri: Uri): Long? =
27
27
  queryOne(contentUri, MediaStore.Video.VideoColumns.DURATION, Cursor::getLong)
28
28
 
29
29
  suspend fun ContentResolver.queryAssetWidth(contentUri: Uri): Int? =
30
- queryOne(contentUri, MediaStore.Images.Media.WIDTH, Cursor::getInt)
30
+ queryOne(contentUri, MediaStore.MediaColumns.WIDTH, Cursor::getInt)
31
31
 
32
32
  suspend fun ContentResolver.queryAssetHeight(contentUri: Uri): Int? =
33
- queryOne(contentUri, MediaStore.Images.Media.HEIGHT, Cursor::getInt)
33
+ queryOne(contentUri, MediaStore.MediaColumns.HEIGHT, Cursor::getInt)
34
34
 
35
35
  suspend fun ContentResolver.queryAssetPath(contentUri: Uri): String? =
36
36
  queryOne(contentUri, MediaStore.Files.FileColumns.DATA, Cursor::getString)
37
37
 
38
38
  suspend fun ContentResolver.queryAssetBucketId(contentUri: Uri): Int? =
39
- queryOne(contentUri, MediaStore.Images.Media.BUCKET_ID, Cursor::getInt)
39
+ queryOne(contentUri, MediaStore.MediaColumns.BUCKET_ID, Cursor::getInt)
40
40
 
41
41
  suspend fun ContentResolver.insertPendingAsset(
42
42
  displayName: String,
@@ -0,0 +1,22 @@
1
+ package expo.modules.medialibrary.next.objects.album
2
+
3
+ import android.content.Context
4
+ import expo.modules.medialibrary.next.exceptions.ContentResolverNotObtainedException
5
+ import expo.modules.medialibrary.next.extensions.getOrThrow
6
+ import expo.modules.medialibrary.next.extensions.resolver.queryAlbumId
7
+ import java.lang.ref.WeakReference
8
+
9
+ class AlbumQuery(context: Context) {
10
+ private val contextRef = WeakReference(context)
11
+
12
+ private val contentResolver
13
+ get() = contextRef
14
+ .getOrThrow()
15
+ .contentResolver ?: throw ContentResolverNotObtainedException()
16
+
17
+ suspend fun getAlbum(title: String): Album? {
18
+ val id = contentResolver.queryAlbumId(title)
19
+ ?: return null
20
+ return Album(id, contextRef.getOrThrow())
21
+ }
22
+ }
@@ -8,6 +8,7 @@ import expo.modules.medialibrary.next.extensions.getOrThrow
8
8
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
9
9
  import expo.modules.medialibrary.next.objects.asset.delegates.AssetLegacyDelegate
10
10
  import expo.modules.medialibrary.next.objects.asset.delegates.AssetModernDelegate
11
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
11
12
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
12
13
  import kotlinx.coroutines.Dispatchers
13
14
  import kotlinx.coroutines.withContext
@@ -42,7 +43,7 @@ class Asset(contentUri: Uri, context: Context) : SharedObject() {
42
43
  suspend fun getWidth(): Int =
43
44
  assetDelegate.getWidth()
44
45
 
45
- suspend fun getMediaType(): String =
46
+ suspend fun getMediaType(): MediaType =
46
47
  assetDelegate.getMediaType()
47
48
 
48
49
  suspend fun getModificationTime(): Long? =
@@ -3,6 +3,7 @@ package expo.modules.medialibrary.next.objects.asset.delegates
3
3
  import android.net.Uri
4
4
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
5
5
  import expo.modules.medialibrary.next.objects.asset.Asset
6
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
6
7
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
7
8
 
8
9
  interface AssetDelegate {
@@ -12,7 +13,7 @@ interface AssetDelegate {
12
13
  suspend fun getFilename(): String
13
14
  suspend fun getHeight(): Int
14
15
  suspend fun getWidth(): Int
15
- suspend fun getMediaType(): String
16
+ suspend fun getMediaType(): MediaType
16
17
  suspend fun getModificationTime(): Long?
17
18
  suspend fun getUri(): Uri
18
19
  suspend fun getMimeType(): MimeType
@@ -19,16 +19,19 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAssetHeight
19
19
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetModificationTime
20
20
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetPath
21
21
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetWidth
22
- import expo.modules.medialibrary.next.extensions.resolver.queryGetCreationTime
22
+ import expo.modules.medialibrary.next.extensions.resolver.queryAssetCreationTime
23
23
  import expo.modules.medialibrary.next.extensions.safeCopy
24
24
  import expo.modules.medialibrary.next.extensions.safeMove
25
25
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
26
26
  import expo.modules.medialibrary.next.objects.asset.Asset
27
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
27
28
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
28
29
  import kotlinx.coroutines.Dispatchers
29
30
  import kotlinx.coroutines.withContext
30
31
  import java.io.File
31
32
  import java.lang.ref.WeakReference
33
+ import kotlin.time.DurationUnit
34
+ import kotlin.time.toDuration
32
35
 
33
36
  @DeprecatedSinceApi(Build.VERSION_CODES.Q)
34
37
  class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
@@ -49,7 +52,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
49
52
 
50
53
  override suspend fun getCreationTime(): Long? {
51
54
  return contentResolver
52
- .queryGetCreationTime(contentUri)
55
+ .queryAssetCreationTime(contentUri)
53
56
  .takeIf { it != 0L }
54
57
  }
55
58
 
@@ -71,7 +74,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
71
74
  val height = contentResolver.queryAssetHeight(contentUri)
72
75
  ?: throw AssetPropertyNotFoundException("Height")
73
76
  // If height is not saved to the database
74
- if (getMediaType().contains("image") && height <= 0) {
77
+ if (getMediaType() == MediaType.IMAGE && height <= 0) {
75
78
  return downloadBitmapAndGet { it.outHeight }
76
79
  }
77
80
  return height
@@ -80,7 +83,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
80
83
  override suspend fun getWidth(): Int {
81
84
  val width = contentResolver.queryAssetWidth(contentUri)
82
85
  ?: throw AssetPropertyNotFoundException("Width")
83
- if (getMediaType().contains("image") && width <= 0) {
86
+ if (getMediaType() == MediaType.IMAGE && width <= 0) {
84
87
  return downloadBitmapAndGet { it.outWidth }
85
88
  }
86
89
  return width
@@ -93,12 +96,14 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
93
96
  return extract(options)
94
97
  }
95
98
 
96
- override suspend fun getMediaType(): String =
97
- contentResolver.getType(contentUri)
98
- ?: throw AssetPropertyNotFoundException("MediaType")
99
+ override suspend fun getMediaType(): MediaType =
100
+ MediaType.fromContentUri(contentUri)
99
101
 
100
102
  override suspend fun getModificationTime(): Long? =
101
- contentResolver.queryAssetModificationTime(contentUri).takeIf { it != 0L }
103
+ contentResolver.queryAssetModificationTime(contentUri)
104
+ ?.takeIf { it != 0L }
105
+ ?.toDuration(DurationUnit.SECONDS)
106
+ ?.inWholeMilliseconds
102
107
 
103
108
  override suspend fun getMimeType(): MimeType {
104
109
  return contentResolver.getType(contentUri)?.let { MimeType(it) }
@@ -18,15 +18,18 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAssetHeight
18
18
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetModificationTime
19
19
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetPath
20
20
  import expo.modules.medialibrary.next.extensions.resolver.queryAssetWidth
21
- import expo.modules.medialibrary.next.extensions.resolver.queryGetCreationTime
21
+ import expo.modules.medialibrary.next.extensions.resolver.queryAssetCreationTime
22
22
  import expo.modules.medialibrary.next.extensions.resolver.updateRelativePath
23
23
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
24
24
  import expo.modules.medialibrary.next.objects.asset.Asset
25
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
25
26
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
26
27
  import kotlinx.coroutines.Dispatchers
27
28
  import kotlinx.coroutines.withContext
28
29
  import java.io.File
29
30
  import java.lang.ref.WeakReference
31
+ import kotlin.time.DurationUnit
32
+ import kotlin.time.toDuration
30
33
 
31
34
  @RequiresApi(Build.VERSION_CODES.Q)
32
35
  class AssetModernDelegate(override val contentUri: Uri, context: Context) : AssetDelegate {
@@ -39,7 +42,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
39
42
 
40
43
  override suspend fun getCreationTime(): Long? {
41
44
  return contentResolver
42
- .queryGetCreationTime(contentUri)
45
+ .queryAssetCreationTime(contentUri)
43
46
  .takeIf { it != 0L }
44
47
  }
45
48
 
@@ -61,7 +64,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
61
64
  val height = contentResolver.queryAssetHeight(contentUri)
62
65
  ?: throw AssetPropertyNotFoundException("Height")
63
66
  // If height is not saved to the database
64
- if (getMediaType().contains("image") && height <= 0) {
67
+ if (getMediaType() == MediaType.IMAGE && height <= 0) {
65
68
  return downloadBitmapAndGet { it.outHeight }
66
69
  }
67
70
  return height
@@ -70,7 +73,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
70
73
  override suspend fun getWidth(): Int {
71
74
  val width = contentResolver.queryAssetWidth(contentUri)
72
75
  ?: throw AssetPropertyNotFoundException("Width")
73
- if (getMediaType().contains("image") && width <= 0) {
76
+ if (getMediaType() == MediaType.IMAGE && width <= 0) {
74
77
  return downloadBitmapAndGet { it.outWidth }
75
78
  }
76
79
  return width
@@ -83,12 +86,14 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
83
86
  return extract(options)
84
87
  }
85
88
 
86
- override suspend fun getMediaType(): String =
87
- contentResolver.getType(contentUri)
88
- ?: throw AssetPropertyNotFoundException("MediaType")
89
+ override suspend fun getMediaType(): MediaType =
90
+ MediaType.fromContentUri(contentUri)
89
91
 
90
92
  override suspend fun getModificationTime(): Long? =
91
- contentResolver.queryAssetModificationTime(contentUri).takeIf { it != 0L }
93
+ contentResolver.queryAssetModificationTime(contentUri)
94
+ ?.takeIf { it != 0L }
95
+ ?.toDuration(DurationUnit.SECONDS)
96
+ ?.inWholeMilliseconds
92
97
 
93
98
  override suspend fun getUri(): Uri {
94
99
  // e.g. storage/emulated/0/Android/data/expo/files/[ROOT_ALBUM]/[ALBUM_NAME]
@@ -35,7 +35,7 @@ class AssetLegacyFactory(context: Context) : AssetFactory {
35
35
  val baseDir = if (relativePath != null) {
36
36
  File(relativePath.toFilePath())
37
37
  } else {
38
- mimeType.externalStoragePublicDirectory()
38
+ mimeType.externalStorageAssetDirectory()
39
39
  }
40
40
  baseDir.mkdirs()
41
41
 
@@ -0,0 +1,31 @@
1
+ package expo.modules.medialibrary.next.objects.query
2
+
3
+ import expo.modules.kotlin.apifeatures.EitherType
4
+ import expo.modules.kotlin.types.Either
5
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
6
+ import expo.modules.medialibrary.next.records.AssetField
7
+ import kotlin.time.DurationUnit
8
+ import kotlin.time.toDuration
9
+
10
+ @OptIn(EitherType::class)
11
+ class MediaStoreQueryFormatter {
12
+ companion object {
13
+ fun parse(field: AssetField, value: Either<MediaType, Long>): String {
14
+ if (value.`is`(MediaType::class)) {
15
+ return parse(value.get(MediaType::class))
16
+ }
17
+ return parse(field, value.get(Long::class))
18
+ }
19
+
20
+ fun parse(field: AssetField, value: Long): String {
21
+ if (field == AssetField.MODIFICATION_TIME) {
22
+ return value.toDuration(DurationUnit.MILLISECONDS).inWholeSeconds.toString()
23
+ }
24
+ return value.toString()
25
+ }
26
+
27
+ fun parse(value: MediaType): String {
28
+ return value.toMediaStoreValue().toString()
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,108 @@
1
+ package expo.modules.medialibrary.next.objects.query
2
+
3
+ import android.content.Context
4
+ import android.os.Build
5
+ import android.provider.MediaStore
6
+ import expo.modules.kotlin.sharedobjects.SharedObject
7
+ import expo.modules.medialibrary.next.exceptions.ContentResolverNotObtainedException
8
+ import expo.modules.medialibrary.next.extensions.asIterable
9
+ import expo.modules.medialibrary.next.extensions.getOrThrow
10
+ import expo.modules.medialibrary.next.extensions.resolver.extractAssetContentUri
11
+ import expo.modules.medialibrary.next.objects.album.Album
12
+ import expo.modules.medialibrary.next.objects.asset.Asset
13
+ import expo.modules.medialibrary.next.objects.query.builder.QueryLegacyExecutor
14
+ import expo.modules.medialibrary.next.objects.query.builder.QueryModernExecutor
15
+ import expo.modules.medialibrary.next.records.AssetField
16
+ import expo.modules.medialibrary.next.records.SortDescriptor
17
+ import kotlinx.coroutines.Dispatchers
18
+ import kotlinx.coroutines.ensureActive
19
+ import kotlinx.coroutines.withContext
20
+ import java.lang.ref.WeakReference
21
+ import kotlin.collections.joinToString
22
+
23
+ class Query(context: Context) : SharedObject() {
24
+ private val contextRef = WeakReference(context)
25
+
26
+ private val contentResolver
27
+ get() = contextRef
28
+ .getOrThrow()
29
+ .contentResolver ?: throw ContentResolverNotObtainedException()
30
+
31
+ private val clauses = mutableListOf<String>()
32
+ private val args = mutableListOf<String>()
33
+ private val orderBy = mutableListOf<SortDescriptor>()
34
+
35
+ private var limit: Int? = null
36
+ private var offset: Int? = null
37
+
38
+ fun eq(field: AssetField, value: String) = apply {
39
+ clauses.add("${field.toMediaStoreColumn()} = ?")
40
+ args.add(value)
41
+ }
42
+
43
+ fun within(field: AssetField, values: List<String>) = apply {
44
+ val questionMarks = values.joinToString(", ") { "?" }
45
+ clauses.add("${field.toMediaStoreColumn()} IN ($questionMarks)")
46
+ args.addAll(values)
47
+ }
48
+
49
+ fun gt(field: AssetField, value: String) = apply {
50
+ clauses.add("${field.toMediaStoreColumn()} > ?")
51
+ args.add(value)
52
+ }
53
+
54
+ fun gte(field: AssetField, value: String) = apply {
55
+ clauses.add("${field.toMediaStoreColumn()} >= ?")
56
+ args.add(value)
57
+ }
58
+
59
+ fun lt(field: AssetField, value: String) = apply {
60
+ clauses.add("${field.toMediaStoreColumn()} < ?")
61
+ args.add(value)
62
+ }
63
+
64
+ fun lte(field: AssetField, value: String) = apply {
65
+ clauses.add("${field.toMediaStoreColumn()} <= ?")
66
+ args.add(value)
67
+ }
68
+
69
+ fun limit(limit: Int) = apply {
70
+ this.limit = limit
71
+ }
72
+
73
+ fun album(album: Album) = apply {
74
+ clauses.add("${MediaStore.MediaColumns.BUCKET_ID} = ?")
75
+ args.add(album.id)
76
+ }
77
+
78
+ fun offset(count: Int) = apply {
79
+ this.offset = count
80
+ }
81
+
82
+ fun orderBy(descriptor: SortDescriptor) = apply {
83
+ orderBy.add(descriptor)
84
+ }
85
+
86
+ suspend fun exe(): List<Asset> = withContext(Dispatchers.IO) {
87
+ val queryExecutor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
88
+ QueryModernExecutor(clauses, args, orderBy, limit, offset)
89
+ } else {
90
+ QueryLegacyExecutor(clauses, args, orderBy, limit, offset)
91
+ }
92
+ val projection = arrayOf(
93
+ MediaStore.Files.FileColumns._ID,
94
+ MediaStore.Files.FileColumns.MEDIA_TYPE
95
+ )
96
+
97
+ val cursor = queryExecutor.exe(projection, contentResolver)
98
+ return@withContext cursor.use {
99
+ ensureActive()
100
+ val idColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
101
+ val typeColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
102
+ it.asIterable()
103
+ .map { row -> row.extractAssetContentUri(idColumn, typeColumn) }
104
+ .map { uri -> Asset(uri, contextRef.getOrThrow()) }
105
+ .toList()
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,8 @@
1
+ package expo.modules.medialibrary.next.objects.query.builder
2
+
3
+ import android.content.ContentResolver
4
+ import android.database.Cursor
5
+
6
+ fun interface QueryExecutor {
7
+ suspend fun exe(projection: Array<String>, contentResolver: ContentResolver): Cursor
8
+ }
@@ -0,0 +1,73 @@
1
+ package expo.modules.medialibrary.next.objects.query.builder
2
+
3
+ import android.content.ContentResolver
4
+ import android.database.Cursor
5
+ import android.os.Build
6
+ import android.provider.MediaStore
7
+ import androidx.annotation.DeprecatedSinceApi
8
+ import expo.modules.medialibrary.next.exceptions.QueryCouldNotBeExecuted
9
+ import expo.modules.medialibrary.next.extensions.resolver.EXTERNAL_CONTENT_URI
10
+ import expo.modules.medialibrary.next.records.SortDescriptor
11
+ import kotlinx.coroutines.Dispatchers
12
+ import kotlinx.coroutines.withContext
13
+
14
+ @DeprecatedSinceApi(Build.VERSION_CODES.Q)
15
+ class QueryLegacyExecutor(
16
+ private val clauses: MutableList<String>,
17
+ private val args: MutableList<String>,
18
+ private val sortDescriptors: MutableList<SortDescriptor>,
19
+ private val limit: Int?,
20
+ private val offset: Int?
21
+ ) : QueryExecutor {
22
+ override suspend fun exe(
23
+ projection: Array<String>,
24
+ contentResolver: ContentResolver
25
+ ): Cursor = withContext(Dispatchers.IO) {
26
+ val selection = buildSelection()
27
+ val sortOrder = buildSortOrder()
28
+ val selectionArgs = args.toTypedArray()
29
+ return@withContext contentResolver.query(EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder)
30
+ ?: throw QueryCouldNotBeExecuted("Cursor is null")
31
+ }
32
+
33
+ private fun buildSortOrder(): String {
34
+ var sortOrder = buildOrderBy()
35
+ sortOrder = addLimit(sortOrder)
36
+ return addOffset(sortOrder)
37
+ }
38
+
39
+ private fun buildSelection(): String {
40
+ return clauses.joinToString(" AND ")
41
+ }
42
+
43
+ private fun buildOrderBy(): String? {
44
+ return if (!sortDescriptors.isEmpty()) {
45
+ sortDescriptors.joinToString(", ") { it.toMediaStoreQueryString() }
46
+ } else {
47
+ null
48
+ }
49
+ }
50
+
51
+ private fun addLimit(sortOrder: String?): String {
52
+ return if (limit != null) {
53
+ requireNotEmptySortOrder(sortOrder) + " LIMIT $limit"
54
+ } else if (offset != null) {
55
+ // SQLITE: limit is required to perform offset
56
+ requireNotEmptySortOrder(sortOrder) + " LIMIT -1"
57
+ } else {
58
+ sortOrder ?: ""
59
+ }
60
+ }
61
+
62
+ private fun requireNotEmptySortOrder(sortOrder: String?): String {
63
+ return sortOrder ?: MediaStore.Files.FileColumns._ID
64
+ }
65
+
66
+ private fun addOffset(orderBy: String): String {
67
+ return if (offset != null) {
68
+ "$orderBy OFFSET $offset"
69
+ } else {
70
+ orderBy
71
+ }
72
+ }
73
+ }