expo-media-library 18.1.1 → 18.2.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 (113) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/medialibrary/next/MediaLibraryNextModule.kt +63 -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/AssetExtensions.kt +8 -5
  6. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/Asset.kt +2 -1
  7. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetDelegate.kt +2 -1
  8. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetLegacyDelegate.kt +5 -5
  9. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetModernDelegate.kt +5 -5
  10. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/MediaStoreQueryFormatter.kt +25 -0
  11. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/Query.kt +109 -0
  12. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryExecutor.kt +8 -0
  13. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryLegacyExecutor.kt +73 -0
  14. package/android/src/main/java/expo/modules/medialibrary/next/objects/query/builder/QueryModernExecutor.kt +72 -0
  15. package/android/src/main/java/expo/modules/medialibrary/next/objects/wrappers/MediaType.kt +55 -0
  16. package/android/src/main/java/expo/modules/medialibrary/next/records/AssetField.kt +23 -0
  17. package/android/src/main/java/expo/modules/medialibrary/next/records/SortDescriptor.kt +14 -0
  18. package/build/next/ExpoMediaLibraryNext.d.ts +4 -1
  19. package/build/next/ExpoMediaLibraryNext.d.ts.map +1 -1
  20. package/build/next/ExpoMediaLibraryNext.js.map +1 -1
  21. package/build/next/MediaLibraryNext.types.d.ts +4 -2
  22. package/build/next/MediaLibraryNext.types.d.ts.map +1 -1
  23. package/build/next/MediaLibraryNext.types.js +4 -2
  24. package/build/next/MediaLibraryNext.types.js.map +1 -1
  25. package/build/next/index.d.ts +4 -1
  26. package/build/next/index.d.ts.map +1 -1
  27. package/build/next/index.js +3 -0
  28. package/build/next/index.js.map +1 -1
  29. package/build/next/types/Asset.d.ts +2 -1
  30. package/build/next/types/Asset.d.ts.map +1 -1
  31. package/build/next/types/Asset.js.map +1 -1
  32. package/build/next/types/AssetField.d.ts +18 -0
  33. package/build/next/types/AssetField.d.ts.map +1 -0
  34. package/build/next/types/AssetField.js +10 -0
  35. package/build/next/types/AssetField.js.map +1 -0
  36. package/build/next/types/GranularPermission.d.ts +2 -0
  37. package/build/next/types/GranularPermission.d.ts.map +1 -0
  38. package/build/next/types/GranularPermission.js +2 -0
  39. package/build/next/types/GranularPermission.js.map +1 -0
  40. package/build/next/types/MediaType.d.ts +7 -0
  41. package/build/next/types/MediaType.d.ts.map +1 -0
  42. package/build/next/types/MediaType.js +8 -0
  43. package/build/next/types/MediaType.js.map +1 -0
  44. package/build/next/types/Query.d.ts +92 -0
  45. package/build/next/types/Query.d.ts.map +1 -0
  46. package/build/next/types/Query.js +2 -0
  47. package/build/next/types/Query.js.map +1 -0
  48. package/build/next/types/SortDescriptor.d.ts +6 -0
  49. package/build/next/types/SortDescriptor.d.ts.map +1 -0
  50. package/build/next/types/SortDescriptor.js +2 -0
  51. package/build/next/types/SortDescriptor.js.map +1 -0
  52. package/expo-module.config.json +1 -1
  53. package/ios/next/MediaLibraryNextModule.swift +64 -6
  54. package/ios/next/exceptions/Exceptions.swift +24 -0
  55. package/ios/next/objects/Query/AssetField.swift +27 -0
  56. package/ios/next/objects/Query/PredicateBuilder.swift +50 -0
  57. package/ios/next/objects/Query/Query.swift +135 -0
  58. package/ios/next/objects/Query/SortDescriptor.swift +11 -0
  59. package/ios/next/objects/asset/Asset.swift +5 -5
  60. package/ios/next/objects/asset/MediaType.swift +38 -0
  61. package/ios/next/repository/AssetRepository.swift +18 -0
  62. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.1/expo.modules.medialibrary-18.1.1-sources.jar → 18.2.0/expo.modules.medialibrary-18.2.0-sources.jar} +0 -0
  63. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0-sources.jar.md5 +1 -0
  64. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0-sources.jar.sha1 +1 -0
  65. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0-sources.jar.sha256 +1 -0
  66. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0-sources.jar.sha512 +1 -0
  67. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.aar +0 -0
  68. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.aar.md5 +1 -0
  69. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.aar.sha1 +1 -0
  70. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.aar.sha256 +1 -0
  71. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.aar.sha512 +1 -0
  72. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.1/expo.modules.medialibrary-18.1.1.module → 18.2.0/expo.modules.medialibrary-18.2.0.module} +22 -22
  73. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.module.md5 +1 -0
  74. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.module.sha1 +1 -0
  75. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.module.sha256 +1 -0
  76. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.module.sha512 +1 -0
  77. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.1.1/expo.modules.medialibrary-18.1.1.pom → 18.2.0/expo.modules.medialibrary-18.2.0.pom} +1 -1
  78. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.pom.md5 +1 -0
  79. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.pom.sha1 +1 -0
  80. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.pom.sha256 +1 -0
  81. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.2.0/expo.modules.medialibrary-18.2.0.pom.sha512 +1 -0
  82. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml +4 -4
  83. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.md5 +1 -1
  84. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha1 +1 -1
  85. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha256 +1 -1
  86. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha512 +1 -1
  87. package/package.json +2 -2
  88. package/src/next/ExpoMediaLibraryNext.ts +4 -1
  89. package/src/next/MediaLibraryNext.types.ts +4 -2
  90. package/src/next/index.ts +5 -2
  91. package/src/next/types/Asset.ts +2 -1
  92. package/src/next/types/AssetField.ts +19 -0
  93. package/src/next/types/GranularPermission.ts +1 -0
  94. package/src/next/types/MediaType.ts +6 -0
  95. package/src/next/types/Query.ts +95 -0
  96. package/src/next/types/SortDescriptor.ts +6 -0
  97. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1-sources.jar.md5 +0 -1
  98. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1-sources.jar.sha1 +0 -1
  99. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1-sources.jar.sha256 +0 -1
  100. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1-sources.jar.sha512 +0 -1
  101. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.aar +0 -0
  102. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.aar.md5 +0 -1
  103. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.aar.sha1 +0 -1
  104. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.aar.sha256 +0 -1
  105. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.aar.sha512 +0 -1
  106. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.module.md5 +0 -1
  107. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.module.sha1 +0 -1
  108. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.module.sha256 +0 -1
  109. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.module.sha512 +0 -1
  110. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.pom.md5 +0 -1
  111. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.pom.sha1 +0 -1
  112. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.pom.sha256 +0 -1
  113. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.1.1/expo.modules.medialibrary-18.1.1.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 18.2.0 — 2025-09-16
14
+
15
+ ### 🎉 New features
16
+
17
+ - [next] Add Query ([#39559](https://github.com/expo/expo/pull/39559) by [@Wenszel](https://github.com/Wenszel))
18
+
13
19
  ## 18.1.1 — 2025-09-10
14
20
 
15
21
  ### 💡 Others
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '18.1.1'
7
+ version = '18.2.0'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.medialibrary"
11
11
  defaultConfig {
12
12
  versionCode 37
13
- versionName "18.1.1"
13
+ versionName "18.2.0"
14
14
  }
15
15
  }
16
16
 
@@ -16,10 +16,16 @@ import expo.modules.medialibrary.next.objects.album.factories.AlbumModernFactory
16
16
  import expo.modules.medialibrary.next.objects.album.factories.AlbumLegacyFactory
17
17
  import expo.modules.medialibrary.next.objects.asset.factories.AssetModernFactory
18
18
  import expo.modules.medialibrary.next.objects.asset.factories.AssetLegacyFactory
19
+ import expo.modules.medialibrary.next.objects.query.MediaStoreQueryFormatter
20
+ import expo.modules.medialibrary.next.objects.query.Query
21
+ import expo.modules.medialibrary.next.objects.wrappers.MediaType
19
22
  import expo.modules.medialibrary.next.permissions.MediaStorePermissionsDelegate
20
23
  import expo.modules.medialibrary.next.permissions.SystemPermissionsDelegate
21
24
  import expo.modules.medialibrary.next.permissions.enums.GranularPermission
25
+ import expo.modules.medialibrary.next.records.AssetField
26
+ import expo.modules.medialibrary.next.records.SortDescriptor
22
27
 
28
+ @OptIn(EitherType::class)
23
29
  class MediaLibraryNextModule : Module() {
24
30
  private val context
25
31
  get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
@@ -141,6 +147,63 @@ class MediaLibraryNextModule : Module() {
141
147
  }
142
148
  }
143
149
 
150
+ Class(Query::class) {
151
+ Constructor {
152
+ Query(context)
153
+ }
154
+
155
+ Function("limit") { self: Query, limit: Int ->
156
+ self.limit(limit)
157
+ }
158
+
159
+ Function("offset") { self: Query, offset: Int ->
160
+ self.offset(offset)
161
+ }
162
+
163
+ Function("album") { self: Query, album: Album ->
164
+ self.album(album)
165
+ }
166
+
167
+ Function("eq") { self: Query, field: AssetField, value: Either<MediaType, Int> ->
168
+ self.eq(field, MediaStoreQueryFormatter.parse(value))
169
+ }
170
+
171
+ Function("within") { self: Query, field: AssetField, values: List<Either<MediaType, Int>> ->
172
+ val stringValues = values.map { value -> MediaStoreQueryFormatter.parse(value) }
173
+ self.within(field, stringValues)
174
+ }
175
+
176
+ Function("gt") { self: Query, field: AssetField, value: Int ->
177
+ self.gt(field, MediaStoreQueryFormatter.parse(value))
178
+ }
179
+
180
+ Function("gte") { self: Query, field: AssetField, value: Int ->
181
+ self.gte(field, MediaStoreQueryFormatter.parse(value))
182
+ }
183
+
184
+ Function("lt") { self: Query, field: AssetField, value: Int ->
185
+ self.lt(field, MediaStoreQueryFormatter.parse(value))
186
+ }
187
+
188
+ Function("lte") { self: Query, field: AssetField, value: Int ->
189
+ self.lte(field, MediaStoreQueryFormatter.parse(value))
190
+ }
191
+
192
+ Function("orderBy") { self: Query, sortDescriptorRef: Either<AssetField, SortDescriptor> ->
193
+ if (sortDescriptorRef.`is`(AssetField::class)) {
194
+ val assetField = sortDescriptorRef.get(AssetField::class)
195
+ val descriptor = SortDescriptor(assetField)
196
+ return@Function self.orderBy(descriptor)
197
+ }
198
+ val descriptor = sortDescriptorRef.get(SortDescriptor::class)
199
+ return@Function self.orderBy(descriptor)
200
+ }
201
+
202
+ AsyncFunction("exe") Coroutine { self: Query ->
203
+ return@Coroutine self.exe()
204
+ }
205
+ }
206
+
144
207
  AsyncFunction("createAsset") Coroutine { filePath: Uri, album: Album? ->
145
208
  systemPermissionsDelegate.requireSystemPermissions(true)
146
209
  return@Coroutine assetFactory.create(filePath, album?.getRelativePath())
@@ -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)
@@ -18,25 +18,28 @@ suspend fun ContentResolver.queryAssetDisplayName(contentUri: Uri): String? =
18
18
  queryOne(contentUri, MediaStore.MediaColumns.DISPLAY_NAME, Cursor::getString)
19
19
 
20
20
  suspend fun ContentResolver.queryGetCreationTime(contentUri: Uri): Long? =
21
- queryOne(contentUri, MediaStore.Images.Media.DATE_TAKEN, Cursor::getLong)
21
+ queryOne(contentUri, MediaStore.MediaColumns.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
+
41
+ suspend fun ContentResolver.queryAssetMediaType(contentUri: Uri): Int? =
42
+ queryOne(contentUri, MediaStore.Files.FileColumns.MEDIA_TYPE, Cursor::getInt)
40
43
 
41
44
  suspend fun ContentResolver.insertPendingAsset(
42
45
  displayName: String,
@@ -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
@@ -24,6 +24,7 @@ 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
@@ -71,7 +72,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
71
72
  val height = contentResolver.queryAssetHeight(contentUri)
72
73
  ?: throw AssetPropertyNotFoundException("Height")
73
74
  // If height is not saved to the database
74
- if (getMediaType().contains("image") && height <= 0) {
75
+ if (getMediaType() == MediaType.IMAGE && height <= 0) {
75
76
  return downloadBitmapAndGet { it.outHeight }
76
77
  }
77
78
  return height
@@ -80,7 +81,7 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
80
81
  override suspend fun getWidth(): Int {
81
82
  val width = contentResolver.queryAssetWidth(contentUri)
82
83
  ?: throw AssetPropertyNotFoundException("Width")
83
- if (getMediaType().contains("image") && width <= 0) {
84
+ if (getMediaType() == MediaType.IMAGE && width <= 0) {
84
85
  return downloadBitmapAndGet { it.outWidth }
85
86
  }
86
87
  return width
@@ -93,9 +94,8 @@ class AssetLegacyDelegate(contentUri: Uri, context: Context) : AssetDelegate {
93
94
  return extract(options)
94
95
  }
95
96
 
96
- override suspend fun getMediaType(): String =
97
- contentResolver.getType(contentUri)
98
- ?: throw AssetPropertyNotFoundException("MediaType")
97
+ override suspend fun getMediaType(): MediaType =
98
+ MediaType.fromContentUri(contentUri)
99
99
 
100
100
  override suspend fun getModificationTime(): Long? =
101
101
  contentResolver.queryAssetModificationTime(contentUri).takeIf { it != 0L }
@@ -22,6 +22,7 @@ import expo.modules.medialibrary.next.extensions.resolver.queryGetCreationTime
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
@@ -61,7 +62,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
61
62
  val height = contentResolver.queryAssetHeight(contentUri)
62
63
  ?: throw AssetPropertyNotFoundException("Height")
63
64
  // If height is not saved to the database
64
- if (getMediaType().contains("image") && height <= 0) {
65
+ if (getMediaType() == MediaType.IMAGE && height <= 0) {
65
66
  return downloadBitmapAndGet { it.outHeight }
66
67
  }
67
68
  return height
@@ -70,7 +71,7 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
70
71
  override suspend fun getWidth(): Int {
71
72
  val width = contentResolver.queryAssetWidth(contentUri)
72
73
  ?: throw AssetPropertyNotFoundException("Width")
73
- if (getMediaType().contains("image") && width <= 0) {
74
+ if (getMediaType() == MediaType.IMAGE && width <= 0) {
74
75
  return downloadBitmapAndGet { it.outWidth }
75
76
  }
76
77
  return width
@@ -83,9 +84,8 @@ class AssetModernDelegate(override val contentUri: Uri, context: Context) : Asse
83
84
  return extract(options)
84
85
  }
85
86
 
86
- override suspend fun getMediaType(): String =
87
- contentResolver.getType(contentUri)
88
- ?: throw AssetPropertyNotFoundException("MediaType")
87
+ override suspend fun getMediaType(): MediaType =
88
+ MediaType.fromContentUri(contentUri)
89
89
 
90
90
  override suspend fun getModificationTime(): Long? =
91
91
  contentResolver.queryAssetModificationTime(contentUri).takeIf { it != 0L }
@@ -0,0 +1,25 @@
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
+
7
+ @OptIn(EitherType::class)
8
+ class MediaStoreQueryFormatter {
9
+ companion object {
10
+ fun parse(value: Either<MediaType, Int>): String {
11
+ if (value.`is`(MediaType::class)) {
12
+ return parse(value.get(MediaType::class))
13
+ }
14
+ return parse(value.get(Int::class))
15
+ }
16
+
17
+ fun parse(value: Int): String {
18
+ return value.toString()
19
+ }
20
+
21
+ fun parse(value: MediaType): String {
22
+ return value.toMediaStoreValue().toString()
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,109 @@
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 album: Album? = null
36
+ private var limit: Int? = null
37
+ private var offset: Int? = null
38
+
39
+ fun eq(field: AssetField, value: String) = apply {
40
+ clauses.add("${field.toMediaStoreColumn()} = ?")
41
+ args.add(value)
42
+ }
43
+
44
+ fun within(field: AssetField, values: List<String>) = apply {
45
+ val questionMarks = values.joinToString(", ") { "?" }
46
+ clauses.add("${field.toMediaStoreColumn()} IN ($questionMarks)")
47
+ args.addAll(values)
48
+ }
49
+
50
+ fun gt(field: AssetField, value: String) = apply {
51
+ clauses.add("${field.toMediaStoreColumn()} > ?")
52
+ args.add(value)
53
+ }
54
+
55
+ fun gte(field: AssetField, value: String) = apply {
56
+ clauses.add("${field.toMediaStoreColumn()} >= ?")
57
+ args.add(value)
58
+ }
59
+
60
+ fun lt(field: AssetField, value: String) = apply {
61
+ clauses.add("${field.toMediaStoreColumn()} < ?")
62
+ args.add(value)
63
+ }
64
+
65
+ fun lte(field: AssetField, value: String) = apply {
66
+ clauses.add("${field.toMediaStoreColumn()} <= ?")
67
+ args.add(value)
68
+ }
69
+
70
+ fun limit(limit: Int) = apply {
71
+ this.limit = limit
72
+ }
73
+
74
+ fun album(album: Album) = apply {
75
+ clauses.add("${MediaStore.MediaColumns.BUCKET_ID} = ?")
76
+ args.add(album.id)
77
+ }
78
+
79
+ fun offset(count: Int) = apply {
80
+ this.offset = count
81
+ }
82
+
83
+ fun orderBy(descriptor: SortDescriptor) = apply {
84
+ orderBy.add(descriptor)
85
+ }
86
+
87
+ suspend fun exe(): List<Asset> = withContext(Dispatchers.IO) {
88
+ val queryExecutor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
89
+ QueryModernExecutor(clauses, args, orderBy, limit, offset)
90
+ } else {
91
+ QueryLegacyExecutor(clauses, args, orderBy, limit, offset)
92
+ }
93
+ val projection = arrayOf(
94
+ MediaStore.Files.FileColumns._ID,
95
+ MediaStore.Files.FileColumns.MEDIA_TYPE
96
+ )
97
+
98
+ val cursor = queryExecutor.exe(projection, contentResolver)
99
+ return@withContext cursor.use {
100
+ ensureActive()
101
+ val idColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
102
+ val typeColumn = it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)
103
+ it.asIterable()
104
+ .map { row -> row.extractAssetContentUri(idColumn, typeColumn) }
105
+ .map { uri -> Asset(uri, contextRef.getOrThrow()) }
106
+ .toList()
107
+ }
108
+ }
109
+ }
@@ -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
+ }
@@ -0,0 +1,72 @@
1
+ package expo.modules.medialibrary.next.objects.query.builder
2
+
3
+ import android.content.ContentResolver
4
+ import android.content.ContentResolver.QUERY_ARG_LIMIT
5
+ import android.content.ContentResolver.QUERY_ARG_OFFSET
6
+ import android.content.ContentResolver.QUERY_ARG_SQL_SELECTION
7
+ import android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS
8
+ import android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER
9
+ import android.database.Cursor
10
+ import android.os.Build
11
+ import android.os.Bundle
12
+ import androidx.annotation.RequiresApi
13
+ import expo.modules.medialibrary.next.exceptions.QueryCouldNotBeExecuted
14
+ import expo.modules.medialibrary.next.extensions.resolver.EXTERNAL_CONTENT_URI
15
+ import expo.modules.medialibrary.next.records.SortDescriptor
16
+ import kotlinx.coroutines.Dispatchers
17
+ import kotlinx.coroutines.withContext
18
+
19
+ @RequiresApi(Build.VERSION_CODES.R)
20
+ class QueryModernExecutor(
21
+ private var clauses: MutableList<String>,
22
+ private var args: MutableList<String>,
23
+ private val sortDescriptors: MutableList<SortDescriptor>,
24
+ private val limit: Int?,
25
+ private val offset: Int?
26
+ ) : QueryExecutor {
27
+ override suspend fun exe(
28
+ projection: Array<String>,
29
+ contentResolver: ContentResolver
30
+ ): Cursor = withContext(Dispatchers.IO) {
31
+ val queryArgs = build()
32
+ return@withContext contentResolver.query(EXTERNAL_CONTENT_URI, projection, queryArgs, null)
33
+ ?: throw QueryCouldNotBeExecuted("Cursor is null")
34
+ }
35
+
36
+ private fun build(): Bundle {
37
+ val selection = buildSelection()
38
+ val selectionArgs = args.toTypedArray()
39
+ val sortOrder = buildSortOrder()
40
+ return Bundle().apply {
41
+ limit?.let { putInt(QUERY_ARG_LIMIT, it) }
42
+ offset?.let {
43
+ // SQLITE: limit is required to perform offset
44
+ if (limit == null) {
45
+ putInt(QUERY_ARG_LIMIT, -1)
46
+ }
47
+ putInt(QUERY_ARG_OFFSET, it)
48
+ }
49
+ selection?.let {
50
+ putString(QUERY_ARG_SQL_SELECTION, it)
51
+ putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
52
+ }
53
+ sortOrder?.let {
54
+ putString(QUERY_ARG_SQL_SORT_ORDER, it)
55
+ }
56
+ }
57
+ }
58
+
59
+ private fun buildSelection(): String? {
60
+ if (clauses.isEmpty()) {
61
+ return null
62
+ }
63
+ return clauses.joinToString(" AND ")
64
+ }
65
+
66
+ private fun buildSortOrder(): String? {
67
+ if (sortDescriptors.isEmpty()) {
68
+ return null
69
+ }
70
+ return sortDescriptors.joinToString(", ") { it.toMediaStoreQueryString() }
71
+ }
72
+ }
@@ -0,0 +1,55 @@
1
+ package expo.modules.medialibrary.next.objects.wrappers
2
+
3
+ import android.net.Uri
4
+ import android.provider.MediaStore
5
+ import expo.modules.kotlin.types.Enumerable
6
+
7
+ enum class MediaType(val value: String) : Enumerable {
8
+ AUDIO("audio"),
9
+ IMAGE("image"),
10
+ VIDEO("video"),
11
+ UNKNOWN("unknown");
12
+
13
+ fun toMediaStoreValue(): Int {
14
+ return when (this) {
15
+ AUDIO -> MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO
16
+ IMAGE -> MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
17
+ VIDEO -> MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
18
+ UNKNOWN -> MediaStore.Files.FileColumns.MEDIA_TYPE_NONE
19
+ }
20
+ }
21
+
22
+ companion object {
23
+ fun fromString(string: String): MediaType {
24
+ return when (string.lowercase()) {
25
+ "audio" -> AUDIO
26
+ "image" -> IMAGE
27
+ "video" -> VIDEO
28
+ else -> UNKNOWN
29
+ }
30
+ }
31
+
32
+ fun fromMediaStoreValue(mediaStoreValue: Int): MediaType {
33
+ return when (mediaStoreValue) {
34
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> AUDIO
35
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> IMAGE
36
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> VIDEO
37
+ else -> UNKNOWN
38
+ }
39
+ }
40
+
41
+ fun fromContentUri(contentUri: Uri): MediaType {
42
+ val pathSegments = contentUri.pathSegments
43
+ if (pathSegments.contains("images")) {
44
+ return IMAGE
45
+ }
46
+ if (pathSegments.contains("video")) {
47
+ return VIDEO
48
+ }
49
+ if (pathSegments.contains("audio")) {
50
+ return AUDIO
51
+ }
52
+ return UNKNOWN
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,23 @@
1
+ package expo.modules.medialibrary.next.records
2
+
3
+ import android.provider.MediaStore
4
+ import expo.modules.kotlin.types.Enumerable
5
+
6
+ enum class AssetField(val key: String) : Enumerable {
7
+ CREATION_TIME("creationTime"),
8
+ MODIFICATION_TIME("modificationTime"),
9
+ MEDIA_TYPE("mediaType"),
10
+ WIDTH("width"),
11
+ HEIGHT("height"),
12
+ DURATION("duration");
13
+
14
+ fun toMediaStoreColumn(): String =
15
+ when (this) {
16
+ CREATION_TIME -> MediaStore.Images.Media.DATE_TAKEN
17
+ MODIFICATION_TIME -> MediaStore.Images.Media.DATE_MODIFIED
18
+ MEDIA_TYPE -> MediaStore.Files.FileColumns.MEDIA_TYPE
19
+ WIDTH -> MediaStore.MediaColumns.WIDTH
20
+ HEIGHT -> MediaStore.MediaColumns.HEIGHT
21
+ DURATION -> MediaStore.Video.VideoColumns.DURATION
22
+ }
23
+ }
@@ -0,0 +1,14 @@
1
+ package expo.modules.medialibrary.next.records
2
+
3
+ import expo.modules.kotlin.records.Field
4
+ import expo.modules.kotlin.records.Record
5
+
6
+ data class SortDescriptor(
7
+ @Field val key: AssetField,
8
+ @Field val ascending: Boolean? = true
9
+ ) : Record {
10
+ fun toMediaStoreQueryString(): String {
11
+ val ascendingString = if (ascending ?: true) "ASC" else "DESC"
12
+ return "${key.toMediaStoreColumn()} $ascendingString"
13
+ }
14
+ }
@@ -1,8 +1,11 @@
1
1
  import { NativeModule } from 'expo-modules-core';
2
- import type { Asset, Album } from './MediaLibraryNext.types';
2
+ import { Album } from './types/Album';
3
+ import { Asset } from './types/Asset';
4
+ import { Query } from './types/Query';
3
5
  declare class ExpoMediaLibraryNextModule extends NativeModule {
4
6
  Asset: typeof Asset;
5
7
  Album: typeof Album;
8
+ Query: typeof Query;
6
9
  }
7
10
  declare const _default: ExpoMediaLibraryNextModule;
8
11
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoMediaLibraryNext.d.ts","sourceRoot":"","sources":["../../src/next/ExpoMediaLibraryNext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,OAAO,0BAA2B,SAAQ,YAAY;IAC3D,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,KAAK,EAAE,OAAO,KAAK,CAAC;CACrB;;AAED,wBAAuF"}
1
+ {"version":3,"file":"ExpoMediaLibraryNext.d.ts","sourceRoot":"","sources":["../../src/next/ExpoMediaLibraryNext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,OAAO,OAAO,0BAA2B,SAAQ,YAAY;IAC3D,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,KAAK,EAAE,OAAO,KAAK,CAAC;CACrB;;AAED,wBAAuF"}